問題:
公司之前有一套文件過濾驅動,但是在實施過程中經常出現問題,現在交由我維護。於是在邊看代碼的過程中,一邊查看官方資料,進行整理。
這套文件過濾驅動的目的只要是根據應用層下發的策略來控制對某些特定文件的控制,例如根據后綴名來決定是否允許查看,是否允許查看指定目錄啊之類的功能。
介紹:
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的文件系統本身就不怎么了解,在這個前提下去做過濾操作,就難上加難了。