Windows 文件過濾驅動經驗總結

Windows 文件過濾驅動經驗總結
做者:sinister

本文轉載自驅動開發網

看了 ChuKuangRen 的第二版《文件過濾驅動開發教程》後,很有感觸。我想,交流都是創建在平等的基礎上,在抱怨氛圍和環境很差的同時應該先想想本身究竟付出了多少?只知索取不肯付出的人也就不用抱怨了,要怪也只能怪本身。發本身心得的人無非是兩種目的,一是引起一些討論,好糾正本身錯誤的認識,以便從中獲取更多的知識使本身進步的更快。二是作一份備忘,當本身遺忘的時候可以立刻找到相關資料。我這裏也總結了近幾年作文件過濾驅動時所積累下來的一些小小經驗,這分筆記也是看了 ChuKuangRen 的教程後,臨時想到的一小部分而已,是想到哪寫到哪,不是很全,若是之後再回想起什麼也會不斷補充。因其工做緣由,近段時間在 SOLARIS 驅動與 Linux 內核方面投入的精力比較多,Windows 下的文件過濾驅動一直也沒有怎麼去碰,因此最後仍是那句老話 FIXME。

 一、得到文件全路徑以及判斷時機

除在全部 IRP_MJ_XXX 以前本身從頭建立 IRP 發送到下層設備查詢全路徑外,不要嘗試在 IRP_MJ_CREATE 之外的地方得到全路徑,由於只有在 IRP_MJ_CREATE
中才會使用 ObCreateObject() 來創建一個有效的 FILE_OBJECT。而在 IRP_READ IRP_WRITE 中它們是直接操做 FCB (File Control Block)的。


 二、從頭創建 IRP 發送關注點

不管你創建什麼樣的 IRP,是 IRP_MJ_CREATE 也好仍是 IRP_MJ_DIRECTORY_CONTROL也罷,最要提醒的就是一些標誌。不一樣的標誌會代來不一樣的結果,有些結果是直接返回失敗。這裏指的標誌不光是 IRP->Flags,還要考慮 IO_STACK_LOCATION->Flags還有其它等等。尤爲是你要達到一些特殊目的,這時候更須要注意,如 IRP_MN_QUERY_DIRECTORY,不一樣的標誌結果有很大的不一樣。


 三、從頭創建 IRP 獲取全路徑注意點

本身從頭創建一個 IRP_MJ_QUERY_INFORMATION 的 IRP 獲取全路徑時須要注意,不只在 IRP_MJ_CREATE 要作區別處理,在 IRP_MJ_CLOSE 也要作一樣的處理,不然若是目標是 NTFS 文件系統的話可能產生 deadlock。若是是 NTFS 那麼在 IRP_MJ_CLEANUP 的時候也須要對 FO_STREAM_FILE 類型的文件作一樣處理。

 四、得到本地/遠程訪問用戶名(域名/SID)

方法只有在 IRP_MJ_CREATE 中才可用,那是由於 IO_SECURITY_CONTEXT 只有在 IO_STACK_LOCATION->Parameters.Create.SecurityContext 纔會有效。這樣你纔有可能從 IO_SECURITY_CONTEXT->SecurityContext->AccessState->SubjectSecurityContext.XXXToken 中得到訪問 TOKEN,從而進一步獲得用戶名或 SID。記得 IFS 中有一個庫,它的 LIB 導出一個函數可讓你在得到以上信息後獲得用戶名與域名。但若是你想兼容 NT4 的話,只能本身分析來得出本地和遠程的 SID。

 五、文件與目錄的判斷

正確的方法在楚狂人的文檔裏已經說過了,再補充一句。若是你的文件過濾驅動要兼容全部文件系統,那麼不要十分相信從 FileObject->FsContext 裏取得的數據。正確的方法仍是在你傳遞下去 IRP_MJ_CREATE 後從最下層文件系統延設備棧返回到你這裏後再得到。

 六、加/解密中判斷點

只判斷 IRP_PAGING_IO,IRP_SYNCHRONOUS_PAGING_IO,IRP_NOCACHE 是沒錯的。若是有問題,相信是本身的問題。關於有人提到在 FILE_OBJECT->Flags中的 FO_NO_INTERMEDIATE_BUFFERING 是否須要判斷,對此問題的回答是隻要你判斷了 IRP_NOCACHE 就不用再判斷 FILE_OBJECT 中的,由於它最終會設置 IRP->Flags 爲 IRP_NOCACHE。關於你看到的諸如 IRP_DEFER_IO_COMPLETION 等 IRP 不要去管它,由於它只是一個過程。最終讀寫仍是如上所介紹。至於以上這些 IRP 哪一個是由 CC MGR 發送的,哪些是由 I/O MGR 發送和在何時發送的,這個已經有不少討論了,相信能夠找到。

 七、舉例說明關於 IRP 傳遞與完成注意事項

只看 Walter Oney 的那本 《Programming the Microsoft Windows driver model》裏介紹的流程,本身沒有實際的體會仍是不夠的,那裏只介紹了基礎概念,讓本身有了知識。知道如何用,在什麼狀況下用,用哪一種方法,可以用的穩定這叫有了技術。咱們從另外一個角度出發,把問題分爲兩段來看,這樣利於總結。一個 IRP 在過濾驅動中,把它分爲須要安裝 CompleteRoutine 的與無需安裝 CompleteRoutine 的。那麼在不須要安裝 CompleteRoutine 的有如下幾類狀況。

(1) 拿到這個 IRP 後什麼都不作,直接調用 IoCompleteRequest() 來返回。
(2) 拿到這個 IRP 後什麼都不作,直接傳遞到底層設備,使用IoSkipCurrentIrpStackLocation() 後調用 IoCallDriver() 傳遞。
(3) 使用 IoBuildSynchronousFsdRequest() 或 IoBuildDeviceIoControlRequest()來創建 IRP 的。

以上幾種根據須要直接使用便可,除了一些參數與標誌須要注意外,沒有什麼系統機制相關的東西須要注意了。那麼再來看須要安裝 CompleteRoutine 的狀況。咱們把這種狀況再細分爲兩種,一是在 CompleteRoutine 中返回標誌爲STATUS_MORE_PROCESSING_REQUIRED 的狀況。二是返回處這個外的標誌,須要使用函數IoMarkIrpPending() 的狀況。在 CompleteRoutine 中絕大多數就這麼兩種狀況,你須要使用其中的一種狀況。那麼爲何須要安裝 CompleteRoutine 呢?那是由於咱們對其 IRP 從上層驅動,通過咱們驅動,在通過底層設備棧返回到咱們這一層驅動時須要獲得其中內容做爲參考依據的,還有對其中內容須要進行修改的。再有一種狀況是沒有通過上層驅動,而 IRP 的產生是在咱們驅動直接下發到底層驅動,而通過設備棧後返回到咱們這一層,且咱們不在但願它繼續向上返回的,由於這個 IRP 自己就不是從上層來的。綜上所述,先來看下 IoMarkIrpPending() 的狀況。

(1) 在 CompleteRoutine 中判斷 Irp->PendingReturned 並使用 IoMarkIrpPending()而後返回。這種方法在沒有使用 KeSetEvent() 的狀況下,且不是自建 IRP 發送到底層驅動返回時使用。也就是說有可能我所作的工做都是在 CompleteRoutine 中進行的。好比加/解密時,我在這裏對下層驅動返回數據的判斷並修改。修改後由於沒有使用 STATUS_MORE_PROCESSING_REQUIRED 標誌,它會延設備堆一直向上返回併到用戶獲得數據爲止。這裏必定要注意,在這種狀況下 CompleteRoutine返回後,不要在碰這個 IRP。也就是說若是這個時候你使用了 IoCompleteRequest()的話會出現一個 MULTIPLE_IRP_COMPLIETE_REQUEST 的 BSOD 錯誤。


(2) 在 CompleteRoutine 中直接返回 STATUS_MORE_PROCESSING_REQUIRED 標誌。這種狀況在使用了 KeSetEvent() 的函數下出現。這裏又有兩個小小的分之。

1) 出現於上層發送到我這裏,當我這裏使用 IoCallDriver() 後,底層返回數據通過我這一層時,我想讓它暫時中止繼續向上傳遞,讓這個 IRP 稍微歇息一會,等我對這個 IRP 返回的數據操做完成後(通常是沒有在 CompleteRoutine中對返回數據進行操做狀況下,也就是說等到完成例程返回後再進行操做),由我來調用 IoCompleteRequest() 讓它延着設備棧繼續返回。這裏要注意,咱們是想讓它返回的,因此調用了 IoCompleteRequest()。這個可不一樣於下面所講的本身從頭分配 IRP 時在 CompleteRoutine 中已經調用 IoFreeIrp() 釋放了當前IRP 的狀況。好比我在作一個改變文件大小,向文件頭寫入加密標誌的驅動時,在上層發來了 IRP_MJ_QUERY_INFORMATION 查詢文件,我想在這個時候得到文件信息進行判斷,而後根據個人判斷結果再移動文件指針。注意:上面是兩步,第一步是先得到文件大小,那麼在這個時候我就須要用到上述辦法,先讓這個 IRP傳遞下去,獲得我想要的東西后在進行對比。等待適當時機完成這個 IRP,讓數據繼續傳遞,直到用戶收到爲止。第二步我會結合下面小節來說。

2) 出現於本身從頭創建 IRP,當使用 IoAllocate() 或 IoBuildAsynchronousFsdRequest()建立 IRP 調用 IoCallDriver() 後,底層返回數據到我這一層時,我不想讓這個 IRP 繼續向上延設備棧傳遞。由於這個 IRP 就是在我這層次創建的,上層本就不知道有這麼一個 IRP。那麼到這裏我就要在 CompleteRoutine 中使用 IoFreeIrp()來釋放掉這個 IRP,並不讓它繼續傳遞。這裏必定要注意,在 CompleteRoutine函數返回後,這個 IRP 已經釋放了,若是這個時候在有任何關於這個 IRP 的操做那麼後果是災難性的,一定致使 BSOD 錯誤。前面 1) 小節給出的例子只完成了第一步這裏繼續講第二步,第一步我重用這個 IRP 獲得了文件大小,那麼這個時候雖然知道大小,但我仍是沒法知道這個文件是否被我加過密。這時,我就須要在這裏本身從頭創建一個 IRP_MJ_READ 的 IRP 來讀取文件來判斷是否我加密過了的文件,若是是,則要減小相應的大小,而後繼續返回。注意:這裏的返回是指讓第一步的IRP 返回。而不是咱們本身建立的。咱們建立的都已經在CompleteRoutine 中銷燬了。

 八、關於完成 IRP 的動做簡介

當一個底層驅動調用了 IoCompleteRequest() 函數時,基本上全部設備棧相關 IRP 處理工做都是在它那裏完成的。包括 IRP->Flags 的一些標誌的判斷,對 APC 的處理,拋出MULTIPLE_IRP_COMPLETE_REQUESTS 錯誤等。當它延設備棧一直調用驅動所安裝的 CompleteRoutine時,若是發現 STATUS_MORE_PROCESSING_REQUIRED 這個標誌,則會中止向上繼續回滾。這也是爲何在 CompleteRoutine 中使用這個標誌便可暫停 IRP 的緣由。

 九、關於 ObQueryNameString 的使用

這個函數的使用,在有些環境下會有問題。它的上層函數是 ZwQueryObject()。在某些狀況下會致使系統掛起,或者直接 BSOD。它是從 對象管理器中的 ObpRootDirectoryObject開始遍歷,經過 OBJECT_HEADER_TO_NAME_INFO 得到對象名稱。今天問了下 PolyMeta好象是在處理 PIPE 時會掛啓,這個問題出如今 2000 系統。在 XP 上好象補丁了。 

 十、關於重入問題

其實這個問題在好久前的 IFS FAQ 裏已經介紹的很清楚,包括處理方法以及每種方法可能帶來的問題。IFS FAQ 裏的 Q34 一共介紹了四種方法,包括本身從頭創建 IRP發送,使用 ShadowDevice,使用特徵字符串,根據線程 ID,在 XP 下使用IoCreateFileSpecifyDeviceObjectHint() 函數。而且把以上幾種在不一樣環境下使用要處理的問題也作了簡單的介紹。且在 Q33 裏介紹了在 CIFS 碰到的 FILE_COMPLETE_IF_OPLOCKED 問題的解決方法。函數

相關文章
相關標籤/搜索