基于Minifilter框架的文件过滤驱动理解
概述
Minifilter即File System Minifilter Drivers,是Windows为了简化第三方开发人员开发文件过滤驱动而提供的一套框架,这个框架依赖于一个称之为Filter Manager(后面简写为FltMgr)的传统文件系统过滤驱动。这套框架应用到内核中的结构如下图所示:
FltMgr以传统文件过滤驱动的形式插入到I/O处理队列中去接收不同的I/O请求,然后将这个请求遍历发布到它所维护的Minifilter对象中,然后根据各个Minifilter对这个I/O请求的处理结果来决定后续的操作。
这种模式在很多软件架构中使用,类似于插件一样,每一个Minifilter遵守一定的接口规范插入到FltMgr中,然后就能执行过滤控制。
上面是对整个Minifilter框架进行描述,实际内部需要注意的细节牵涉到很多内核知识,比如中断级别、I/O请求被封装成IRP包和fast I/O请求等相对复杂的概念。这里不做深究,主要以研究Minifilter的开发过程和相应注意的地方为主。此外还有FltMgr在内核中的位置等相关知识,也只在必要的地方提及一下。
Minifilter流程
Minifilter通过注册并启动后,根据在注册表中设置的相应值,插入到对应的FltMgr实例(Frame)队列中,然后关联上需要过滤的卷。FltMgr会根据Minifilter所注册的I/O操作类型,调用对应的pre和post操作函数,并根据对应的返回值执行不同的流程。这里假设有A、B、C三个Minifilter从上往下挂载在FltMgr实例上,那么当接收到I/O请求时,执行的步骤为A-pre、B-pre、C-pre,一旦某个Minifilter返回了FLT_PREOP_COMPLETE,即表明这个I/O请求被它完成处理了,则立即按照相反的顺序调用对应的post函数(不再继续往下调)。
注册和关联
Minifilter根据FltRegisterFilter函数介绍说明,为响应操作设置对应的回调函数和数据结构后,就可以在DriverEntry中调用FltRegisterFilter进行注册了,然后调用FltStartFiltering来通知FltMgr当前Minifilter已经准备好关联到要过滤的卷了。
FltMgr会在系统启动时或者接收到手动关联命令时,调用Minifilter的InstanceSetupCallback回调函数,当然前提是Minifilter在注册的时候增设置了这个函数的地址,这个回调函数会传入要关联的卷标信息,FltMgr会根据这个函数的返回值判断是否关联成功。这里和对应卷相关联的Minifilter实例叫做instance。一个卷上可以挂多个instance。只有对指定卷进行关联后,才能过滤其I/O请求。
取消关联的过程可以理解成释放实例的过程,释放后就无法继续过滤对应卷了。
Pre回调函数(PFLT_PRE_OPERATION_CALLBACK)
FLT_PREOP_COMPLETE:表示当前的过滤驱动完成了本次I/O操作,过滤管理器就不再往下发送本次I/O请求,而是依次向上调用post回调函数。这种情况下IoStatus.Status的值就是最终I/O操作的执行结果(不能是STATUS_PENDING)。
FLT_PREOP_SUCCESS_NO_CALLBACK/FLT_PREOP_SUCCESS_WITH_CALLBACK:这个返回值表示处理成功,让过滤管理器去做自己的事,区别在于WITH_CALLBACK的返回值会标明需要回调post函数。而NO_CALLBACK的则标明不需要。
FLT_PREOP_PENDING:顾名思义,表明当前过滤驱动将本次I/O操作挂起了,过滤管理器需要等待当前驱动调用FltCompletePendedPreOperation函数后才会继续本次I/0操作处理流程。注意只有对于基于IRP中断的I/O操作(用FLT_IS_IRP_OPERATION宏测试)才可以挂起。
FLT_PREOP_DISALLOW_FASTIO:只有操作是fast I/O操作(用FLT_IS_FASTIO_OPERATION(Data)进行测试)时才可以返回这个值,表明过滤驱动不允许fast I/O操作继续执行。因此过滤管理器不会再下发该请求,而是依次向上调用post回调函数。这种情况下不需要设置IoStatus.Status的值,过滤管理器会自动设置这个值。
FLT_PREOP_SYNCHRONIZE:这个返回值表明处理未完成,保持当前过滤驱动上下文线程环境,交由过滤管理器继续下发后调用post回调函数后继续处理。也只对基于IRP中断的操作有效,并且必须有post函数,如果不是基于IRP中断的,就会和FLT_PREOP_SUCCESS_WITH_CALLBACK一样。注意:对于Create操作,不应该返回这个值,因为文件管理器已经为这个操作进行同步了。此外对于同步的读和写操作,如果返回这个值会严重影响驱动和系统性能。
如果在pre和post函数中更改了Data的内容,必须调用FltSetCallbackDataDirty函数(更改IoStatus除外)。
Post回调函数(PFLT_POST_OPERATION_CALLBACK)
这个回调函数执行的中断等级为IRQL <= DISPATCH_LEVEL。所以需要注意以下几点:1、不能安全调用必须低于IRQL级别的任何内核模式的派遣函数。2、在这个函数内开辟的任何数据结构必须位于非页内存。3、该函数不可分页。4、不能请求资源(resource)、信号量(mutextes)和快速信号量(fast mutexes),只能获取互斥锁(spin lock)。5、不能获取、设置或者删除上下文,但可以释放上下文。
相对于Pre回调函数多了最后一个参数Flags,这个参数如果存在FLTFL_POST_OPERATION_DRAINING标记位,则表明当前过滤驱动实例正在被取消关联,本次调用是为了清理pre回调函数传入的completion context,返回值必须是FLT_POSTOP_FINISHED_PROCESSING,并且这个回调是在IRQL<=APC_LEVEL执行的。
FLT_POSTOP_FINISHED_PROCESSING:表明本过滤驱动完成了对I/0操作的处理并将控制交还给过滤管理器。
FLT_POSTOP_MORE_PROCESSING_REQUIRED:只有当微过滤驱动将本次I/O操作发送到工作队列中时,才能返回这个值,微过滤驱动最后必须负责完成这个I/O操作。过滤管理器会继续等待FltCompletePendedPostOperation函数被调用后才继续执行控制。注意:这个返回值也必须对基于IRP中断的操作执行。
任何要被执行在IRQL<DISPATCH_LEVEL的I/O完成处理都不能在这个回调函数内直接执行。取而代之,可以使用FltDoCompletionRpocessingWhenSafe或者FltQueueDeferredIoWorkItem之类的函数发送到工作队列中。
除了以下情况外,要确保在Flags参数没有FLTFL_POST_OPERATION_DRAINING标记位的时候才能调用FltDoCompletionRpocessingWhenSafe函数:
1、如果微过滤驱动的pre回调函数为一个基于IRP中断的操作返回FLT_PREOP_SYNCHRONIZE,那么对应的post回调要保证和pre回调都处在IRQL<=APC_LEVEL的线程上下文中。
2、对于create操作的post回调要保证中断级别为IRQL_PASSIVE_LEVEL(原始IRP_MJ_CREATE操作所处的线程上下文)。