experiment : 基于wdk's cancelsafe 例程, 拒绝文件创建

来源:互联网 发布:怎么找网络喷子 编辑:程序博客网 时间:2024/06/07 18:01

WDK7600中提供了cancelSafe例程, 用于演示如何将挂起的IRP操作放入挂住队列.

但是例子中在挂住队列WorkItem中的操作只是超时, 超时后, 还是会让文件继续按照原来的请求继续操作.


我想做的是: 在基于MiniFilter的主防中, 遇到有r3请求的文件后缀文件被创建时, 将拦到的IRP操作插入挂住队列,返回挂住状态. 

然后由r3决断, 是否对该文件创建操作放行还是拒绝.


网上找了很长时间, 没有找到基于minifilter + cancelsafe, 对文件操作进行干预的Demo。

网上的一般做法都是拦到文件操作时, 直接让r3裁决. 这样可能有效率问题, 没有充分利用minfilter提供的挂住机制 .


今天实验出来了.

发现在挂住队列的WorkItem中, 需要同时设置CALL_BACK_DATA 和 NTSTATUS, 然后调用 FltCompletePendedPreOperation

然后就看见效果(拒绝文件创建或允许文件创建).


我的工程基于WDK提供的miniflter那几个Demo, 根据需要整理了一下, 和原始Demo有点不像了~

下面是有关于 minifilter + cancelSafe 干预文件操作的代码片段, 

DataQueue.h, DataQueue.c : cancelsafe操作中的挂住队列操作整理在这2个文件中.


/// @file       DataQueue.h/// @brief      数据队列的定义#ifndef __DATA_QUEUE_H__#define __DATA_QUEUE_H__#include <fltKernel.h>#include <dontuse.h>#include <suppress.h>#include "constDefine.h"///  Queue context data structuretypedef struct _QUEUE_CONTEXT{    FLT_CALLBACK_DATA_QUEUE_IO_CONTEXT CbdqIoContext;    /// 队列中的文件上报信息    TAG_FILE_OPT_INFO   fileInfo;} QUEUE_CONTEXT, *PQUEUE_CONTEXT;/// Instance context data structuretypedef struct _INSTANCE_CONTEXT{    /// Instance for this context.    PFLT_INSTANCE Instance;    /// Cancel safe queue members    FLT_CALLBACK_DATA_QUEUE Cbdq;    LIST_ENTRY QueueHead;    FAST_MUTEX Lock;    /// Flag to control the life/death of the work item thread    volatile LONG WorkerThreadFlag;    /// Notify the worker thread that the instance is being torndown    KEVENT TeardownEvent;} INSTANCE_CONTEXT, *PINSTANCE_CONTEXT;/*++Routine Description:    FltMgr calls this routine to acquire the lock protecting the queue.Arguments:    DataQueue - Supplies a pointer to the queue itself.    Irql - Returns the previous IRQL if a spinlock is acquired.  We do not use           any spinlocks, so we ignore this.Return Value:    None.--*/VOIDCbdqAcquire(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __out PKIRQL Irql);/*++Routine Description:    FltMgr calls this routine to release the lock protecting the queue.Arguments:    DataQueue - Supplies a pointer to the queue itself.    Irql - Supplies the previous IRQL if a spinlock is acquired.  We do not use           any spinlocks, so we ignore this.Return Value:    None.--*/VOIDCbdqRelease(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in KIRQL Irql    );/*++Routine Description:    FltMgr calls this routine to insert an entry into our pending I/O queue.    The queue is already locked before this routine is called.Arguments:    DataQueue - Supplies a pointer to the queue itself.    Data - Supplies the callback data for the operation that is being           inserted into the queue.    Context - Supplies user-defined context information.Return Value:    STATUS_SUCCESS if the function completes successfully.  Otherwise a valid    NTSTATUS code is returned.--*/NTSTATUSCbdqInsertIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in PFLT_CALLBACK_DATA Data,    __in_opt PVOID Context    );/*++Routine Description:    FltMgr calls this routine to remove an entry from our pending I/O queue.    The queue is already locked before this routine is called.Arguments:    DataQueue - Supplies a pointer to the queue itself.    Data - Supplies the callback data that is to be removed.Return Value:    None.--*/VOIDCbdqRemoveIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in PFLT_CALLBACK_DATA Data    );/*++Routine Description:    FltMgr calls this routine to look for an entry on our pending I/O queue.    The queue is already locked before this routine is called.Arguments:    DataQueue - Supplies a pointer to the queue itself.    Data - Supplies the callback data we should start our search from.           If this is NULL, we start at the beginning of the list.    PeekContext - Supplies user-defined context information.Return Value:    A pointer to the next callback data structure, or NULL.--*/PFLT_CALLBACK_DATACbdqPeekNextIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in_opt PFLT_CALLBACK_DATA Data,    __in_opt PVOID PeekContext    );/*++Routine Description:    FltMgr calls this routine to complete an operation as cancelled that was    previously pended. The queue is already locked before this routine is called.Arguments:    DataQueue - Supplies a pointer to the queue itself.    Data - Supplies the callback data that is to be canceled.Return Value:    None.--*/VOIDCbdqCompleteCanceledIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __inout PFLT_CALLBACK_DATA Data    );/*++Routine Description:    This WorkItem routine is called in the system thread context to process    all the pended I/O in this mini filter's cancel safe queue. For each I/O    in the queue, it completes the I/O after pending the operation for a    period of time. The thread exits when the queue is empty.Arguments:    WorkItem - Unused.    Filter - Unused.    Context - Context information.Return Value:    None.--*/VOIDPreIrpMjCreateWorkItemRoutine(    __in PFLT_GENERIC_WORKITEM WorkItem,    __in PFLT_FILTER Filter,    __in PVOID Context    );/*++Routine Description:    This routine waits for a period of time or until the instance is    torndown.Arguments:    InstanceContext - Supplies a pointer to the instance context.Return Value:    The return value is the status of the operation.--*/NTSTATUSPreIrpMjCreatePendIo(    __in PINSTANCE_CONTEXT InstanceContext    );/// @fn     PreIrpMjCreateProcessIo/// @brief  如果要拒绝, 必须要同时设置Data和callbackStatus, 如下组合///         pData->IoStatus.Status = STATUS_ACCESS_DENIED;///         *pcbStatus = FLT_PREOP_COMPLETE;/// @param  PFLT_CALLBACK_DATA pData///         Supplies the callback data that was removed from the queue./// @param  PINSTANCE_CONTEXT pInstCtx///         上下文, 有事件 TeardownEvent 等待r3通知/// @param  FLT_PREOP_CALLBACK_STATUS * pcbStatus///         回调的状态/// @return NTSTATUS///         返回值用不到, 都设置在pData和cbStatus中NTSTATUSPreIrpMjCreateProcessIo(    PFLT_CALLBACK_DATA pData,    PINSTANCE_CONTEXT pInstCtx,    FLT_PREOP_CALLBACK_STATUS * pcbStatus);/*++Routine Description:    This routine empties the cancel safe queue and complete all the    pended pre-read operations.Arguments:    InstanceContext - Supplies a pointer to the instance context.Return Value:    None.--*/VOIDPreIrpMjCreateEmptyQueueAndComplete(    __in PINSTANCE_CONTEXT InstanceContext    );/// @fn     ConvertR3Answer/// @brief  将r3回答的动作码, 转换成r0的IO状态码NTSTATUS ConvertR3Answer(DWORD dwAnswerByR3);#endif  // #ifndef __DATA_QUEUE_H__


/// @file       DataQueue.c/// @brief      Cbdq callback routines#include "DataQueue.h"#include "DebugDefine.h"#include "constDefine.h"#include "timeWrapper.h"#include "LockOpt.h"extern const UNICODE_STRING CbdqFile;VOIDCbdqAcquire(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __out PKIRQL Irql    ){    PINSTANCE_CONTEXT InstCtx;    UNREFERENCED_PARAMETER( Irql );    DebugTrace( DBGLOG_CBDQ_CALLBACK,                ("[%ws]: CbdqAcquire\n",                DRIVER_NAME) );    ///  Get a pointer to the instance context.    InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq );    ///  Acquire the lock.    ExAcquireFastMutex( &InstCtx->Lock );}VOIDCbdqRelease(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in KIRQL Irql    ){    PINSTANCE_CONTEXT InstCtx;    UNREFERENCED_PARAMETER( Irql );    DebugTrace( DBGLOG_CBDQ_CALLBACK,                ("[%ws]: CbdqRelease\n",                DRIVER_NAME) );    ///  Get a pointer to the instance context.    InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq );    ///  Release the lock.    ExReleaseFastMutex( &InstCtx->Lock );}NTSTATUSCbdqInsertIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in PFLT_CALLBACK_DATA Data,    __in_opt PVOID Context    ){    PINSTANCE_CONTEXT InstCtx;    PFLT_GENERIC_WORKITEM WorkItem = NULL;    NTSTATUS Status = STATUS_SUCCESS;    BOOLEAN WasQueueEmpty;    UNREFERENCED_PARAMETER( Context );    DebugTrace( DBGLOG_CBDQ_CALLBACK,                ("[%ws]: CbdqInsertIo\n",                DRIVER_NAME) );    /// Get a pointer to the instance context.    InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq );    /// Save the queue state before inserting to it.    WasQueueEmpty = IsListEmpty( &InstCtx->QueueHead );    /// Insert the callback data entry into the queue.    InsertTailList( &InstCtx->QueueHead,                    &Data->QueueLinks );    /// Queue a work item if no worker thread present.    if (WasQueueEmpty        && (1 == InterlockedIncrement( &InstCtx->WorkerThreadFlag )))    {        WorkItem = FltAllocateGenericWorkItem();        if (WorkItem)        {            Status = FltQueueGenericWorkItem( WorkItem,                                              InstCtx->Instance,                                              PreIrpMjCreateWorkItemRoutine,                                              DelayedWorkQueue,                                              InstCtx->Instance );            if (!NT_SUCCESS(Status))            {                DebugTrace( DBGLOG_CBDQ_CALLBACK | DBGLOG_ERR,                            ("[%ws]: Failed to queue the work item (Status = "                            "0x%x)\n",                            DRIVER_NAME,                            Status) );                FltFreeGenericWorkItem( WorkItem );            }        }        else            Status = STATUS_INSUFFICIENT_RESOURCES;        if (!NT_SUCCESS(Status))        {            /// Remove the callback data that was inserted into the queue.            RemoveTailList( &InstCtx->QueueHead );        }    }    return Status;}VOIDCbdqRemoveIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in PFLT_CALLBACK_DATA Data    ){    UNREFERENCED_PARAMETER( DataQueue );    DebugTrace( DBGLOG_CBDQ_CALLBACK,                ("[%ws]: CbdqRemoveIo\n",                DRIVER_NAME));    ///  Remove the callback data entry from the queue.    RemoveEntryList(&Data->QueueLinks);}PFLT_CALLBACK_DATACbdqPeekNextIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __in_opt PFLT_CALLBACK_DATA Data,    __in_opt PVOID PeekContext    ){    PINSTANCE_CONTEXT InstCtx;    PLIST_ENTRY NextEntry;    PFLT_CALLBACK_DATA NextData;    UNREFERENCED_PARAMETER( PeekContext );    DebugTrace( DBGLOG_CBDQ_CALLBACK,                ("[%ws]: CbdqPeekNextIo\n",                DRIVER_NAME) );    ///  Get a pointer to the instance context.    InstCtx = CONTAINING_RECORD( DataQueue, INSTANCE_CONTEXT, Cbdq );    ///  If the supplied callback "Data" is NULL, the "NextIo" is the first     /// entry in the queue; or it is the next list entry in the queue.    if (Data == NULL)        NextEntry = InstCtx->QueueHead.Flink;    else        NextEntry =  Data->QueueLinks.Flink;    ///  Return NULL if we hit the end of the queue or the queue is empty.    if (NextEntry == &InstCtx->QueueHead)        return NULL;    NextData = CONTAINING_RECORD( NextEntry, FLT_CALLBACK_DATA, QueueLinks );    return NextData;}VOIDCbdqCompleteCanceledIo(    __in PFLT_CALLBACK_DATA_QUEUE DataQueue,    __inout PFLT_CALLBACK_DATA Data    ){    PQUEUE_CONTEXT QueueCtx;    UNREFERENCED_PARAMETER( DataQueue );    DebugTrace( DBGLOG_CBDQ_CALLBACK,                ("[%ws]: CbdqCompleteCanceledIo\n",                DRIVER_NAME) );    QueueCtx = (PQUEUE_CONTEXT) Data->QueueContext[0];    /// Just complete the operation as canceled.    Data->IoStatus.Status = STATUS_CANCELLED;    Data->IoStatus.Information = 0;    FltCompletePendedPreOperation( Data,                                   FLT_PREOP_COMPLETE,                                   0 );    /// Free the extra storage that was allocated for this canceled I/O.    ExFreeToNPagedLookasideList( &g_FilterData.OueueContextLookaside,                                 QueueCtx );}VOIDPreIrpMjCreateWorkItemRoutine(    __in PFLT_GENERIC_WORKITEM WorkItem,    __in PFLT_FILTER Filter,    __in PVOID Context    ){    PINSTANCE_CONTEXT   InstCtx =   NULL;    PFLT_CALLBACK_DATA  Data    =   NULL;        PFLT_INSTANCE Instance = (PFLT_INSTANCE)Context;    PQUEUE_CONTEXT QueueCtx;    NTSTATUS Status;    FLT_PREOP_CALLBACK_STATUS callbackStatus;    UNREFERENCED_PARAMETER(WorkItem);    UNREFERENCED_PARAMETER(Filter);    DebugTrace( DBGLOG_CBDQ_PRE_OPT,                ("[%ws]: PreIrpMjCreateWorkItemRoutine\n",                DRIVER_NAME) );    /// Get a pointer to the instance context.    Status = FltGetInstanceContext( Instance,                                    &InstCtx );    if (!NT_SUCCESS( Status ))    {        ASSERT( !"Instance Context is missing" );        return;    }    ///  Process all the pended I/O in the cancel safe queue    for (;;)    {        callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;        /// 因为没有办法不动挂住的队列, 这函数不干活        PreIrpMjCreatePendIo(InstCtx);        ///  WorkerThreadFlag >= 1;        ///  Here we reduce it to 1.        InterlockedExchange( &InstCtx->WorkerThreadFlag, 1 );        ///  Remove an I/O from the cancel safe queue.        Data = FltCbdqRemoveNextIo( &InstCtx->Cbdq,                                    NULL);        if (Data)        {            QueueCtx = (PQUEUE_CONTEXT) Data->QueueContext[0];            /// 通知r3裁决, 或自行判断            PreIrpMjCreateProcessIo(Data, InstCtx, &callbackStatus);            /// Check to see if we need to lock the user buffer.            /// If the FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag is set we don't             /// have to lock the buffer because its already a system buffer.            /// If the MdlAddress is NULL and the buffer is a user buffer,             /// then we have to construct one in order to look at the buffer.            /// If the length of the buffer is zero there is nothing to read,            /// so we cannot construct a MDL.            if ( !FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_SYSTEM_BUFFER) &&                  Data->Iopb->Parameters.Read.MdlAddress == NULL &&                 Data->Iopb->Parameters.Read.Length > 0 )            {                Status = FltLockUserBuffer(Data);                if (!NT_SUCCESS(Status))                {                    /// If could not lock the user buffer we cannot                    /// allow the IO to go below us. Because we are                     /// in a different VA space and the buffer is a                    /// user mode address, we will either fault or                     /// corrpt data                    DebugTrace( DBGLOG_CBDQ_PRE_OPT | DBGLOG_ERR,                                ("[%ws]: Failed to lock user buffer "                                "(Status = 0x%x)\n",                                DRIVER_NAME,                                Status) );                    callbackStatus = FLT_PREOP_COMPLETE;                    Data->IoStatus.Status = Status;                }            }            ///  Complete the I/O            FltCompletePendedPreOperation( Data,                                           callbackStatus,                                           NULL );            /// Free the extra storage that was allocated for this I/O.            ExFreeToNPagedLookasideList( &g_FilterData.OueueContextLookaside,                                         QueueCtx );        }        else        {            /// At this moment it is possible that a new IO is being inserted            /// into the queue in the CbdqInsertIo routine. Now that the queue             /// is empty, CbdqInsertIo needs to make a decision on whether to             /// create            /// a new worker thread. The decision is based on the race between            /// the InterlockedIncrement in CbdqInsertIo and the            /// InterlockedDecrement as below. There are two situations:            /// (1) If the decrement executes earlier before the increment,            ///     the flag will be decremented to 0 so this worker thread            ///     will return. Then CbdqInsertIo will increment the flag            ///     from 0 to 1, and therefore create a new worker thread.            /// (2) If the increment executes earlier before the decrement,            ///     the flag will be first incremented to 2 in CbdqInsertIo            ///     so a new worker thread will not be satisfied. Then the            ///     decrement as below will lower the flag down to 1, and            ///     therefore continue this worker thread.            if (InterlockedDecrement( &InstCtx->WorkerThreadFlag ) == 0)                break;        }    }    /// Clean up    FltReleaseContext(InstCtx);    FltFreeGenericWorkItem(WorkItem);}NTSTATUSPreIrpMjCreatePendIo(    __in PINSTANCE_CONTEXT InstanceContext    ){    UNREFERENCED_PARAMETER(InstanceContext);        /// 用户做裁决时, 不能动 InstanceContext->Cbdq    /// 没有找到 FltCbdqGet之类的函数, 只有下面这些    /*    FltCbdqInitialize    FltCbdqEnable    FltCbdqDisable    FltCbdqInsertIo    FltCbdqRemoveIo    FltCbdqRemoveNextIo     */    /// 所以不能在这让用户裁决, 调整 : 在 PreIrpMjCreateProcessIo 做裁决    return STATUS_SUCCESS;}NTSTATUSPreIrpMjCreateProcessIo(    PFLT_CALLBACK_DATA pData,    PINSTANCE_CONTEXT pInstCtx,    FLT_PREOP_CALLBACK_STATUS * pcbStatus){    LARGE_INTEGER   DueTime;    NTSTATUS        Status      =   STATUS_SUCCESS;    PQUEUE_CONTEXT  pQueueCtx    =   NULL;    DebugTrace(DBGLOG_CDO_OPT, (">> PreIrpMjCreateProcessIo\r\n"));    /// 为了防止和r3操作 g_FilterData 产生冲突, 需要加锁    LockResourceExclusive(&g_FilterData.ResourceWithR3);    /// 判断 应用层是否传来了通讯通知的事件句柄    /// 如果不能和r3通讯, 设置pData状态为(允许且没有后续回调)    if (NULL == g_FilterData.g_pEventObject)    {        pData->IoStatus.Status = STATUS_SUCCESS;        goto _PreIrpMjCreateProcessIo_END;    }    /// 将 pData 数据分享为一个结构, 带锁操作    pQueueCtx = (PQUEUE_CONTEXT)pData->QueueContext[0];    g_FilterData.g_pFileOptInfo = &pQueueCtx->fileInfo;    /// 状态值初值(pQueueCtx.fileInfo.dwAnswerByR3)已经为允许,    /// 如果R3没应答, 默认是放行的    /// 复位队列等待事件    g_FilterData.pTeardownEvent = &pInstCtx->TeardownEvent;    KeResetEvent(&pInstCtx->TeardownEvent);    /// 设置R3事件, 通知R3来拿    KeSetEvent(g_FilterData.g_pEventObject, 0, FALSE);    UnLockResource(&g_FilterData.ResourceWithR3);    /// Delay R3_ANSWER_TIME_OUT_BY_SECOND seconds     /// or get signaled if the instance is torndown.    DueTime.QuadPart = GetSecondCnt(R3_ANSWER_TIME_OUT_BY_SECOND);    DebugTrace(DBGLOG_CDO_OPT, (">> KeWaitForSingleObject\r\n"));    Status = KeWaitForSingleObject( &pInstCtx->TeardownEvent,                                    Executive,                                    KernelMode,                                    FALSE,                                    &DueTime);    DebugTrace(DBGLOG_CDO_OPT, ("<< KeWaitForSingleObject\r\n"));    LockResourceExclusive(&g_FilterData.ResourceWithR3);        /// 将状态值设置进pData, 由队列处理工作线程做后续处理    /// 根据用户决断, pData 和 cbStatus 都要设置!!!    pData->IoStatus.Status = ConvertR3Answer(            pQueueCtx->fileInfo.dwAnswerByR3);                *pcbStatus = (STATUS_ACCESS_DENIED == pData->IoStatus.Status) ?                FLT_PREOP_COMPLETE : FLT_PREOP_SUCCESS_NO_CALLBACK;    /// 数据已经被r3取过了, 清掉                /// r3接到通知后, 如果(NULL != g_FilterData.g_pFileOptInfo), 会来取数据    g_FilterData.g_pFileOptInfo = NULL;    g_FilterData.pTeardownEvent = NULL; ///< r3收完数据,不会通知驱动了_PreIrpMjCreateProcessIo_END:    UnLockResource(&g_FilterData.ResourceWithR3);    return Status;}NTSTATUS ConvertR3Answer(DWORD dwAnswerByR3){    NTSTATUS status =   STATUS_SUCCESS;        switch(dwAnswerByR3)    {        case R3_ANSWER_REFUSE:            status = STATUS_ACCESS_DENIED;            break;                case R3_ANSWER_PASS:        default:            status = STATUS_SUCCESS;            break;    }    return status;}VOIDPreIrpMjCreateEmptyQueueAndComplete(    __in PINSTANCE_CONTEXT InstanceContext    ){    NTSTATUS status;    FLT_PREOP_CALLBACK_STATUS callbackStatus;    PFLT_CALLBACK_DATA Data;    PQUEUE_CONTEXT QueueCtx;    do {        callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;        Data = FltCbdqRemoveNextIo( &InstanceContext->Cbdq,                                    NULL );        if (Data)        {            QueueCtx = (PQUEUE_CONTEXT) Data->QueueContext[0];            /// Check to see if we need to lock the user buffer.            /// If the FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag is set we don't             /// have to lock the buffer because its already a system buffer.            /// If the MdlAddress is NULL and the buffer is a user buffer,             /// then we have to construct one in order to look at the buffer.            /// If the length of the buffer is zero there is nothing to read,            /// so we cannot construct a MDL.            if ( !FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_SYSTEM_BUFFER) &&                  Data->Iopb->Parameters.Read.MdlAddress == NULL &&                 Data->Iopb->Parameters.Read.Length > 0 )            {                status = FltLockUserBuffer( Data );                if ( ! NT_SUCCESS( status ) )                {                    /// If could not lock the user buffer we cannot                    /// allow the IO to go below us. Because we are                     /// in a different VA space and the buffer is a                    /// user mode address, we will either fault or                     /// corrpt data                                       callbackStatus = FLT_PREOP_COMPLETE;                    Data->IoStatus.Status = status;                }            }            FltCompletePendedPreOperation( Data,                                           callbackStatus,                                           NULL );            ExFreeToNPagedLookasideList( &g_FilterData.OueueContextLookaside,                                         QueueCtx );        }    } while (Data);}




原创粉丝点击