问题:
公司之前有一套文件过滤驱动,但是在实施过程中经常出现问题,现在交由我维护。于是在边看代码的过程中,一边查看官方资料,进行整理。
这套文件过滤驱动的目的只要是根据应用层下发的策略来控制对某些特定文件的控制,例如根据后缀名来决定是否允许查看,是否允许查看指定目录啊之类的功能。
介绍:
MSDN上对可安装的文件系统驱动介绍http://msdn.microsoft.com/en-us/library/windows/hardware/ff548143(v=vs.85).aspx:其中树形结构菜单可以看出可安装的文件驱动类型种类:
文件系统驱动那说的应该就是最基本的驱动了,过滤驱动则是完全基于文件驱动中的操作进行手动过滤,可以理解成你hook了系统的create函数,然后如果你要拦截这个操作你就按要求返回不允许,否则就传给系统下一层去继续就好了。
而minifilter驱动则是通过向过滤管理器(Filter Manager)驱动进行注册自己需要过滤的一些操作,提供指定格式的回调函数让过滤管理器来进行调用即可。对于每一种操作,minifilter都可以注册一个“过滤前”和“过滤后”被调用的回调函数。(有点类似于Linux内核上probe调试技术)。(preCreate和postCreate用来作为后面“过滤前”和“过滤后”被调用的回调函数的例子)。
要注意minifilter的加载顺序是根据一个Altitude来决定的,可以理解成驱动所处的维度。例如,当FilterManager检测到要执行create的时候,就去调用注册了对该操作进行过滤的minifilter的回调函数,根据Altitude的值从大到小的顺序先调用preCreate函数。而FilterManager检测到create执行完成后,便按照Altitude的值从小到大的顺序去调用postCreate函数。
minifilter是基于FilterManager所提供的类似插件一样的模块,它可以调用FilterManager所提供很多功能:http://msdn.microsoft.com/en-us/library/windows/hardware/ff541613(v=vs.85).aspx
加载和卸载:
FilterLoad和FilterUnload可用于在用户态程序控制minifilter驱动的加载和卸载。
这里要注意的是minifilter还存在一个instance的概念,这个instance应该就是指某个特定的minifilter,比如有三个minifilter A、B、C。就代表三个minifilter,每一个instance有特定的名字、altitude和flags。当在instance里面调用FltStartFiltering的时候就会去通知FilterManager去将这个insance关联到磁盘以及相关的I/0操作过滤表上。如果在FLtStartFiltering返回之前,FilterManager检测新挂上的磁盘的话,就会自动通知这个instance(通过调用回调函数InstanceSetupCallBack),所以minifilter注册时提供的Instance开头的回调函数,主要是用于FilterManager对这个驱动的一些反馈操作。(比如关联磁盘啊、取消关联啊之类的)。
此外,minifilter自己主动去卸载的话,当然是调用FltUnregisterFilter函数了。
资料:
英语能力确实不太好,在看后面的I/O操作的介绍的时候看的实在费解。幸好有搜到中文资料:http://blog.csdn.net/jununfly/article/category/517002。还是挺感慨很多高深底层的东西都只有英文资料。我就不多做口舌甚至好有可能误导了。
WDK示例:在WDK开发包示例中的IFSK Samples类别下。源码目录是src/filesys/minifilter。中文介绍:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=24504987&id=208208
可能根据我的需求,比较拥有的应该是以上三个例子了,其中swapBuffers那个,用来做加解密比较适合参考。
记录:
记录下我自己遇到的问题:
1、想要做个最简单的测试,通过vs来直接调试,于是简单的仅仅过滤IRP_MJ_CREATE操作,并且在Pre函数里得到操作的文件名,然后输出。而对于FLT_REGISTRATION结构体中,其他可选的回调函数全部置NULL。效果是根本不会进入到的我的pre函数中,原因是我压根没有attach我的驱动的instance到相关卷标上。(这里可以增进对instance这概念的理解,也就是说必须在相关卷标上attach了对应的instance才会生效)。所以如果在系统启动通过fltmc load命令或者FilterLoad和FltLoadFilter来手动加载驱动的,需要手动去关联minifilter到本地磁盘上。可以用fltmc attach命令、FilterAttach、FltAttachVolume、FilterAttachAtAltitude、FltAttachVolumeAtAltitude来手动加载。
2、原来的过滤驱动代码中,是在InstanceSetup中根据需要关联的卷来获取磁盘信息等,保存到Instance的上下文中。如果开启了磁盘控制,还要对USB类型的磁盘设备名称进行记录。话说这里为什么不在磁盘驱动里面记录呢??
3、原来的代码里创建上下文的地方,既调用了FltAllocateContext也调用了FltSet***Context函数的,如果都成功的话,需要调用两次FltReleaseContext,而实际只调用了一次。
上下文(context):
流上下文(stream context):为文件流所设置的上下文,需要和打开了的文件对象关联,所以不能在pre-create回调函数中直接关联,但是可以在pre-create中创建,然后通过pre-create的最后一个参数传递到post-create中进行调用FltSetStreamContext进行关联。
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操作所处的线程上下文)。
Buffer传输方式
注意IRP_MJ_READ和IRP_MJ_WRITE可以是基于IRP或fast I/O的操作.当它们基于IRP时,buffering方法由以上描述的设备对象标记决定.当它们是fast I/O,它们总是使用neither I/O.更多关于可以是基于IRP或fast I/O的操作的I/O操作的信息,参考可以是IRP-I/O或Fast I/O的操作.
总结:
很遗憾直接写上了总结,事实上上面的内容已经是半个多月前写的了,一方面是有其他事情,一方面发现有点滥竽充数的感觉,没有内容可写,写出来也比较肤浅。在调试的过程中不断出现问题,不断的更改,很多东西改好了也未必能解释出来。
主要原因还是对windows的文件系统本身就不怎么了解,在这个前提下去做过滤操作,就难上加难了。