【转】缓存管理器的一处需要注意的小细节
转自盟主的文章
在研究改别人的一个过滤驱动的时候,发现有的人会在pre Create回调里调用IoCreateFileSpecifyDeviceObjectHint打开Create参数里的文件名,并且用ZwReadFile读取文件并判断是否为pe文件。这种做法会导致一些小问题。
其中过滤驱动并没处理fast io。然后我发现pre write回调偶尔会收不到消息。仔细跟踪下去,发现write消息发送了,但参数里的fileobject并不是Create回调里产生的,而居然是IoCreateFileSpecifyDeviceObjectHint产生的文件对象。
而这个文件对象会有什么问题呢?
见如下堆栈:
f9efe9cc 804e4d77 FastFat!FatFsdWrite
f9efe9dc 804f0b8d nt!IopfCallDriver+0x31
f9efe9f0 804f0756 nt!IoSynchronousPageWrite+0xaf
f9efeacc 804f050d nt!MiFlushSectionInternal+0x38b
f9efeb08 804f0e18 nt!MmFlushSection+0x1e0
f9efeb90 f81e3043 nt!CcFlushCache+0x372
f9efebb8 f81d6006 Fastfat!FatFlushFile+0x20
f9efec0c f81d8bfc Fastfat!FatCommonFlushBuffers+0x12c
f9efec50 804e4d77 Fastfat!FatFsdFlushBuffers+0x3e
f9efec60 f8034432 nt!IopfCallDriver+0x31
f9efec78 f80338a2 xxx!IoCallDriverEx+0x22
f9efec98 f802a722 xxx!OnDispatch+0x142
f9efecb4 804e4d77 xxx!OnDriverDispatch+0x52
f9efecc4 8056b9ab nt!IopfCallDriver+0x31
f9efecd8 8057a8be nt!IopSynchronousServiceTail+0x60
f9efed54 804e006b nt!NtFlushBuffersFile+0x1c1
f9efed54 7c92eb94 nt!KiFastCallEntry+0xf8
0012eef4 7c92d9d6 ntdll!KiFastSystemCallRet
奇怪的是,IoSynchronousPageWrite并没有进入我们的过滤驱动而是直接到了文件系统上。继续跟踪,发现IoSynchronousPageWrite-》
IoGetRelatedDeviceObject的时候,会有这么段代码:
if (deviceObject->AttachedDevice != NULL) {
if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION) {
PIOP_FILE_OBJECT_EXTENSION fileObjectExtension =
(PIOP_FILE_OBJECT_EXTENSION)(FileObject + 1);
ASSERT(!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN));
if (fileObjectExtension->TopDeviceObjectHint != NULL &&
IopVerifyDeviceObjectOnStack(deviceObject, fileObjectExtension->TopDeviceObjectHint)) {
return fileObjectExtension->TopDeviceObjectHint;
}
}
deviceObject = IoGetAttachedDevice( deviceObject );
}
居然返回了TopDeviceObjectHint字段的值。这个值在哪填充,又是什么用途呢?跟踪一下,原来在IoCreateFileSpecifyDeviceObjectHint
-》ObOpenObjectByName的时候会openPacket->TopDeviceObjectHint = DeviceObject;原来这个字段表示需要“命中”的对象。
那么又有疑问了,系统中理论上只有我们的过滤驱动调用了IoCreateFileSpecifyDeviceObjectHint,但是我们调用IoCreateFileSpecifyDeviceObjectHint产生的文件对象,已经在ZwReadFile被ZwClose了呀,按理说应该会被销毁。
所以继续跟踪下去,发现果然如此,堆栈如下:
f70c9508 804e58f6 nt!ObfReferenceObject+0x28
f70c9548 f700de62 nt!CcInitializeCacheMap+0x1a2
f70c9614 f700869a Fastfat!FatCommonRead+0x561
f70c9684 804eedf9 Fastfat!FatFsdRead+0x13d
f70c9694 80574b42 nt!IopfCallDriver+0x31
f70c96a8 80571b98 nt!IopSynchronousServiceTail+0x60
f70c9750 8053d808 nt!NtReadFile+0x580
f70c9750 804ff0d1 nt!KiFastCallEntry+0xf8
f70c97ec f6ea188d nt!ZwReadFile+0x11
f70c9850 f6ebc09c xxx!IsPeFile+0x119
f70c9864 f6ea0177 xxx!MyFileCreate+0x99
f70c99d8 f6ea0d8f xxx!OnCreate+0x415
f70c99f8 f6e993a7 xxx!OnDispatch+0xb2
f70c9a14 804eedf9 xxx!OnDriverDispatch+0x4b
f70c9a24 805783bc nt!IopfCallDriver+0x31
f70c9b04 805787da nt!IopParseDevice+0xa58
f70c9b3c 805b420d nt!IopParseFile+0x46
f70c9bc4 805b0b3f nt!ObpLookupObjectName+0x119
f70c9c18 8056b133 nt!ObOpenObjectByName+0xeb
f70c9c94 8056baaa nt!IopCreateFile+0x407
也就是说CcInitializeCacheMap里会把这个IoCreateFileSpecifyDeviceObjectHint产生的文件对象缓存起来。由于调用了ObfReferenceObject,所以ZwClose的时候并没被销毁。
所以事情就明朗了,原来我们在过滤驱动的create回调里调用IoCreateFileSpecifyDeviceObjectHint产生的文件对象,会在
ZwReadFile的时候被CcInitializeCacheMap缓存起来,以后再有文件读写,都是走入了缓存管理器,而缓存管理器在真正需要读写磁盘的
时候,会使用第一次捕获并缓存的文件对象来发送IRP。而这个文件对象,由于我们是用IoCreateFileSpecifyDeviceObjectHint产生的,所以带有TopDeviceObjectHint字段,表示直接命中文件系统。所以缓存管理器绕过了我们的过滤驱动。
那么解决方案是什么呢?有几种方案。比如处理好fast io,这样无论什么write方式都会被捕获到。当然,还有种更简单的,就是在IoCreateFileSpecifyDeviceObjectHint的时候,对CreateOptions 加上FILE_NO_INTERMEDIATE_BUFFERING 参数。这个参数会让ZwReadFile组装irp的时候,在Irp->Flags里带上IRP_NOCACHE标志。而这个标志会让文件系统的FatCommonRead不调用CcInitializeCacheMap而是直接走FatNonCachedIo。所以也不会缓存我们的文件对象了。
不过加了FILE_NO_INTERMEDIATE_BUFFERING 参数会有些效率上的问题。不过对我来说足够了。修改驱动后放在虚拟机里测试,发现果然收到write消息了,今天一早上的功夫没白费~~
也许本文有分析的不对的地方,欢迎大家指正