緩衝區處理
任何驅動程式中最常見的錯誤之一與緩衝區處理有關,其中緩衝區無效或太小。 這些錯誤可以允許緩衝區溢位或造成系統當機,這可能會危害系統安全性。 本文討論一些緩衝區處理常見問題,以及如何避免這些問題。 它也會識別示範適當緩衝區處理技術的 WDK 範例程序代碼。
緩衝區類型和無效的位址
從驅動程序的觀點來看,緩衝區有兩個品種之一:
分頁緩衝區,可能或可能不在記憶體中。
非分頁緩衝區,其必須位於記憶體中。
無效的記憶體位址不會分頁或未分頁。 當操作系統能夠解決因不正確的緩衝區處理所造成的頁面錯誤時,它會採取下列步驟:
它會將無效的位址隔離成其中一個「標準」位址範圍(分頁核心位址、未分頁的核心位址或用戶位址)。
它會引發適當的錯誤類型。 系統一律會透過錯誤檢查來處理緩衝區錯誤,例如PAGE_FAULT_IN_NONPAGED_AREA或例外狀況,例如STATUS_ACCESS_VIOLATION。 如果錯誤是錯誤檢查,系統將會停止作業。 在例外狀況的情況下,系統會叫用堆棧型例外狀況處理程式。 如果沒有例外狀況處理程式處理例外狀況,系統就會叫用錯誤檢查。
不管怎樣,應用程式程式可以呼叫的任何存取路徑,導致驅動程式導致錯誤檢查,都是驅動程式內的安全性違規。 這類違規可讓應用程式對整個系統造成阻斷服務攻擊。
常見的假設和錯誤
此區域中最常見的問題之一是驅動程式寫入器對操作環境假設太多。 一些常見的假設和錯誤包括:
驅動程式只會檢查位址中是否已設定高位。 依賴固定位模式來判斷位址類型不適用於所有系統或案例。 例如,當系統使用 四 GB 微調 (4GT) 時,此檢查不適用於 x86 型電腦。 使用 4GT 時,使用者模式位址會為地址空間的第三 GB 設定高位。
驅動程式僅使用 ProbeForRead 和 ProbeForWrite 來驗證位址。 這些呼叫可確保位址在探查時是有效的使用者模式位址。 不過,不保證此位址在探查作業之後會保持有效。 因此,這項技術引進了一種微妙的競爭狀況,可能導致週期性無法重現的當機。
ProbeForRead 和 ProbeForWrite 呼叫仍然需要。 如果驅動程式省略探查,用戶可以傳入有效的內核模式位址,即
__try
和__except
區塊 (結構化例外狀況處理) 不會攔截,因而開啟大型安全性漏洞。底線是需要探查和結構化例外狀況處理:
探查會驗證位址是否為使用者模式位址,且緩衝區的長度在用戶位址範圍內。
__try/__except
區塊可防範存取。
請注意, ProbeForRead 只會驗證地址和長度是否落在可能的使用者模式位址範圍內(例如,對於沒有 4GT 的系統略低於 2 GB),而不是記憶體位址是否有效。 相反地, ProbeForWrite 會嘗試存取指定長度的每個頁面中的第一個字節,以確認這些位元組是有效的記憶體位址。
依賴記憶體管理員函式的驅動程式,例如 MmIsAddressValid ,以確保位址有效。 如探查函式所述,這種情況引進了會導致無法產生損毀的競爭條件。
驅動程式無法使用結構化例外狀況處理。 編譯
__try/except
程式內的函式會使用操作系統層級支援來處理例外狀況。 核心層級例外狀況會透過呼叫 ExRaiseStatus 或其中一個相關函式,擲回系統。 驅動程式無法在引發例外狀況的任何呼叫周圍使用結構化例外狀況處理,將會導致錯誤檢查(通常是KMODE_EXCEPTION_NOT_HANDLED)。針對預期不會引發錯誤的程式代碼使用結構化例外狀況處理是錯誤的錯誤。 此使用方式只會遮罩會發現的實際 Bug。 將包裝函式放在
__try/__except
例程的最上層分派層級並不是此問題的正確解決方案,雖然有時候是驅動程式寫入器嘗試的反射解決方案。假設使用者記憶體的內容會維持穩定狀態的驅動程式。 例如,假設驅動程式將值寫入使用者模式記憶體位置,然後在稍後的相同例程中參考該記憶體位置。 惡意應用程式可能會在寫入之後主動修改該記憶體,因此導致驅動程序當機。
對於文件系統而言,這些問題十分嚴重,因為文件系統通常依賴直接存取用戶緩衝區(METHOD_NEITHER傳輸方法)。 這類驅動程式會直接操作用戶緩衝區,因此必須納入緩衝處理的預防措施,以避免操作系統層級損毀。 快速 I/O 一律會傳遞原始記憶體指標,因此如果支援快速 I/O,驅動程式必須防範類似問題。
緩衝區處理的範例程序代碼
WDK 包含 fastfat 和 CDFS 檔案系統驅動程式範例程式代碼中的許多緩衝區驗證範例,包括:
fastfat\deviosup.c 中的 FatLockUserBuffer 函式會使用 MmProbeAndLockPages 來鎖定使用者緩衝區後面的實體頁面,並在 FatMapUserBuffer 中鎖定 MmGetSystemAddressForMdlSafe,以建立鎖定頁面的虛擬對應。
fastfat\fsctl.c 中的 FatGetVolumeBitmap 函式會使用 ProbeForRead 和 ProbeForWrite 來驗證重組 API 中的用戶緩衝區。
cdfs\read.c 中的 CdCommonRead 函式會使用
__try
程式代碼前後__except
的零用戶緩衝區。 CdCommonRead 中的範例程式代碼似乎使用try
和except
關鍵詞。 在 WDK 環境中,C 中的這些關鍵詞會以編譯程式延伸__try
模組和__except
來定義。 任何使用C++程序代碼的人都必須使用原生編譯程式類型來正確處理例外狀況,如同__try
C++ 關鍵詞,但不是 C 關鍵詞,而且會提供對核心驅動程式無效的C++例外狀況處理形式。