虫趣:当NV显卡驱动碰上Verifier
引用注明>>?【作者:張佩】【原文:www.YiiYee.cn/blog】
今天開電腦的時候,剛完成用戶登陸,就遇到一個藍屏。桌面還沒有進去呢。趁著系統正處于抓取dump文件的過程中,趕緊拍了一張照,留作紀念。造成藍屏的不是別人,乃是負責圖形渲染和顯示的顯卡驅動:Nvidia顯卡驅動。
我使用的系統:Win Blue x64。
事出有因
顯卡驅動負責桌面系統的渲染和顯示,其重要性不是一點點。所以輕易是不可能藍屏的。我剛開始也有點納悶,想自己昨天究竟做了什么,使得自己一大早的就遇到個天雷滾滾從天降——開機藍屏。后來看了系統顯示的錯誤原因,才明白過來。從上圖中看到,這是在Verifier開啟的前提下,誘發出來的NV顯卡驅動的癲癇病。
Verifier為啥會開啟呢?我想起來了,這確實事出有因。可追溯到我昨天看的一篇介紹數字簽名的文章,它介紹了sigverif.exe這個工具,可以檢測系統中已安裝而未被數字簽名的驅動程序。在運行了這個工具后,我很欣喜地發現系統中所有驅動程序都是簽過名的。
其實這個結果解釋了我的一個疑惑。因為我記得自己前段時間在運行Verifier的時候,它總是能找到兩個未簽名的驅動程序。其中一個是我安裝的VClone虛擬光驅軟件附帶的內核驅動程序。從VClone的官方文檔來看,它是有數字簽名的,設備管理器中也有正確的顯示。為什么Verifier把它列為嫌犯,我對此一直都很疑惑。
現在有了sigverif作為對照,我又再次運行了Verifier。選擇驗證未簽名的驅動程序,果然還是有兩個被列了出來。如下圖所示。
就這個情況,我研究了半天,沒有一個結論。但過程中,我出現了一個小小的操作失誤。在選擇驅動程序進行驗證的時候,我選擇了一個不可逆的驗證:自動選擇這臺計算機上安裝的所有驅動程序。
我選擇這一項的初衷,是要看看verifier檢索到的驅動列表,和Sigverif檢索的驅動列表的區別。不料這個過程竟然是不可逆的,即使我退出后,再次選擇“刪除現有設置”,也已經沒有用。
但當時,我卻沒這么覺得。我以為通過一些動作,已經把Verifier設置都清空了。其實卻不然呢。這正是發生今天這個問題的初始緣由了。
自我救贖
重啟后,我又試了兩次,希冀可以登錄到桌面后快速地關掉Verifier。但事實卻很無情,我又多遇到了兩次迅捷無比的藍屏。所以我就進入到安全模式。Windows在安全模式下不使用IHV的顯示驅動,而是加載微軟自己的display only顯示驅動。
這一次我是安全的。安全模式救了我。我運行verifier,并在此選擇“刪除現有設置”項。在提示重啟出現的時候,我服從并重啟。重啟到正常的系統,這次已無問題了。
調試分析
活過來后,我第一個啟動的是Windbg,并加載dump文件。錯誤類型DRIVER_VERIFIER_DETECTED_VIOLATION對應的BSOD號是0xC4,自動分析結果如下:
2: kd> !analyze -v ******************************************************************** * * Bugcheck Analysis * * * *******************************************************************DRIVER_VERIFIER_DETECTED_VIOLATION (c4) A device driver attempting to corrupt the system has been caught. This is because the driver was specified in the registry as being suspect (by the administrator) and the kernel has enabled substantial checking of this driver. If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will be among the most commonly seen crashes. Arguments: Arg1: 00000000000000f6, Referencing user handle as KernelMode. Arg2: 0000000000000100, Handle value being referenced. Arg3: ffffe00008f53900, Address of the current process. Arg4: fffff800028fc879, Address inside the driver that is performing the incorrect reference.Debugging Details: ------------------自動分析的結果非常重要。它的第一個參數指明了Verifier錯誤類型,0xf6表示驅動程序在引用一個用戶句柄的時候,把它的類型錯誤地指示為KernelMode。打開Windbg的幫助文檔,看到更詳細的參數解釋:
| Parameter 1 | Parameter 2 | Parameter 3 | Parameter 4 | Cause of Error |
| 0xF6 (Windows 7 operating systems and later) | Handle value being referenced | Address of the current process | Address inside the driver that performs the incorrect reference | A driver references a user-mode handle as kernel mode. |
從上面得到另一個很重要的信息:這個錯誤類型,只在Win7以后的系統上才存在。
它的第三個參數是被應用的句柄,值為0x100。它很明顯是一個用戶層句柄,因為Winidows系統上的內核句柄,其高位是被置1的。比如32位系統上,內核句柄應該是0x80xxxxxx,64位系統上是0xffffffff’80xxxxxx。雖然沒有明確的文檔說明這一點,但僅僅根據我們的觀察,可以從經驗上證明之。
所以自動分析是言之有物的,它是在說:在一個地址為ffffe00008f53900(參數3)的用戶進程環境中, NV顯卡驅動在代碼執行到地址fffff800028fc879(參數4)附近時,以kernelMode的方式使用了一個用戶句柄0x100。
2: kd> !handle 0x100PROCESS ffffe00008f53900SessionId: 1 Cid: 1538 Peb: 7ff725f06000 ParentCid: 0c20DirBase: 17bda2000 ObjectTable: ffffc00003321400 HandleCount: Image: rundll32.exeHandle Error reading handle count.0100: Object: ffffc000056b72a0 GrantedAccess: 00020019 (Protected) (Inherit) (Audit) Entry: ffffc000033b0400 Object: ffffc000056b72a0 Type: (ffffe00000119730) KeyObjectHeader: ffffc000056b7270 (new version)HandleCount: 1 PointerCount: 32768Directory Object: 00000000 Name: \REGISTRY\MACHINE\SYSTEM\CONTROLSET001\CONTROL\CLASS\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000\NVSPCAPS2: kd> !process ffffe00008f53900 PROCESS ffffe00008f53900SessionId: 1 Cid: 1538 Peb: 7ff725f06000 ParentCid: 0c20DirBase: 17bda2000 ObjectTable: ffffc00003321400 HandleCount: Image: rundll32.exeVadRoot ffffe00008d27330 Vads 61 Clone 0 Private 515. Modified 15118. Locked 0.DeviceMap ffffc000039412a0Token ffffc00003359060ElapsedTime 00:00:01.055UserTime 00:00:00.000KernelTime 00:00:00.000QuotaPoolUsage[PagedPool] 150496QuotaPoolUsage[NonPagedPool] 7792Working Set Sizes (now,min,max) (1608, 50, 345) (6432KB, 200KB, 1380KB)PeakWorkingSetSize 1608VirtualSize 74 MbPeakVirtualSize 74 MbPageFaultCount 1630MemoryPriority BACKGROUNDBasePriority 8CommitCharge 620THREAD ffffe00008ec8080 Cid 1538.153c Teb: 00007ff725f0e000 Win32Thread: fffff901469e8b70 RUNNING on processor 2Not impersonatingDeviceMap ffffc000039412a0Owning Process ffffe00008f53900 Image: rundll32.exeAttached Process N/A Image: N/AWait Start TickCount 8751 Ticks: 50 (0:00:00:00.781)Context Switch Count 593 IdealProcessor: 2 UserTime 00:00:00.000KernelTime 00:00:00.140Win32 Start Address 0x00007ff725f33f0cStack Init ffffd000235d7c90 Current ffffd000235d6f90Base ffffd000235d8000 Limit ffffd000235d2000 Call 0Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5Child-SP RetAddr Call Siteffffd000`235d6658 fffff800`d7eea6a8 nt!KeBugCheckExffffd000`235d6660 fffff800`d7eeff98 nt!VerifierBugCheckIfAppropriate+0x3cffffd000`235d66a0 fffff800`d7db7e73 nt!VfCheckUserHandle+0x1b8ffffd000`235d6780 fffff800`d7c285d5 nt! ?? ::NNGAKEGL::`string'+0x10503ffffd000`235d6820 fffff800`d7c402d6 nt!ObReferenceObjectByHandle+0x25ffffd000`235d6870 fffff800`d79d74b3 nt!NtQueryValueKey+0x136ffffd000`235d6b20 fffff800`d79cf900 nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd000`235d6b90)ffffd000`235d6d28 fffff800`028fc879 nt!KiServiceLinkageffffd000`235d6d30 fffff800`028fc0c2 nvlddmkm+0x9a879ffffd000`235d6de0 fffff800`02946cbe nvlddmkm+0x9a0c2ffffd000`235d6e80 fffff800`029151d4 nvlddmkm+0xe4cbeffffd000`235d6f00 fffff800`02907e5c nvlddmkm+0xb31d4ffffd000`235d6f50 fffff800`0317c1c3 nvlddmkm+0xa5e5cffffd000`235d7360 fffff800`0290730b nvlddmkm!nvDumpConfig+0x29fdebffffd000`235d73a0 fffff800`0315e9b9 nvlddmkm+0xa530bffffd000`235d74c0 fffff800`031fcc05 nvlddmkm!nvDumpConfig+0x2825e1ffffd000`235d7590 fffff800`02301e5c nvlddmkm!nvDumpConfig+0x32082dffffd000`235d75c0 fffff800`022c8e03 dxgkrnl!DXGADAPTER::DdiEscape+0x48ffffd000`235d75f0 fffff960`001813a3 dxgkrnl!DxgkEscape+0x573ffffd000`235d7ab0 fffff800`d79d74b3 win32k!NtGdiDdDDIEscape+0x53ffffd000`235d7b00 00007ff8`349d14aa nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd000`235d7b00)0000006b`439bf068 00000000`00000000 0x00007ff8`349d14aa這個進程rundll32是一個執行dll調用的通用的宿主進程,所以它的父進程比較能說明問題。我期望它的父進程是NV相關的進程,但最后發現CID為0xc20的進程為桌面進程。可能的情況是桌面進程調用了D3D的相關功能,進入NV顯卡驅動并爆發了問題。在進入內核驅動時,用戶程序傳入了一個句柄參數,這個句柄指向一個和NV顯卡相關的注冊表鍵而,內核不正確地使用了這個句柄并導致問題。這個相關的注冊表鍵值的路徑為:\REGISTRY\MACHINE\SYSTEM\CONTROLSET001\CONTROL\CLASS\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000\NVSPCAPS
2: kd> !process 0 0 *省略其它進程信息* PROCESS ffffe00008810900SessionId: 1 Cid: 0c20 Peb: 7ff7be8a6000 ParentCid: 0c10DirBase: 15a40e000 ObjectTable: ffffc000041a5440 HandleCount: Image: explorer.exe逆推代碼錯誤
根據上面的分析內容,已能很輕松地指癥了。從它的調用棧上可以看出來,在進入Verifier檢測函數前,系統調用的函數是ObReferenceObjectByHandle。我們看這個函數的聲明:
NTSTATUS ObReferenceObjectByHandle(_In_ HANDLE Handle,_In_ ACCESS_MASK DesiredAccess,_In_opt_ POBJECT_TYPE ObjectType,_In_ KPROCESSOR_MODE AccessMode,_Out_ PVOID *Object,_Out_opt_ POBJECT_HANDLE_INFORMATION HandleInformation );關于AccessMode,MSDN上的解釋是:
AccessMode?[in]
Specifies the access mode to use for the access check. It must be either?UserMode?or?KernelMode. Drivers should always specify?UserMode?for handles they receive from user address space.
所以,對于0x100的用戶句柄,如果在調用ObReferenceObjectByHandle的時候,指示的Accessmode為KernelMode,就會在Verifier檢驗函數中產生一個類型為0xC4/0xF6的BSOD。這也是一個比較合乎情理的錯誤原因。
到這里問題到還沒有結束,因為ObReferenceObjectByHandle是被間接調用的,NV驅動直接調用的函數是ZwQueryValueKey(它沒有AccessMode這個參數)。為什么是ZwQueryValueKey呢?這涉及到Zwxxx和Ntxxx兩組系統API的區別。見下面這一段stack。
ffffd000`235d6870 fffff800`d79d74b3 nt!NtQueryValueKey+0x136 ffffd000`235d6b20 fffff800`d79cf900 nt!KiSystemServiceCopyEnd+0x13 ffffd000`235d6d28 fffff800`028fc879 nt!KiServiceLinkage ffffd000`235d6d30 fffff800`028fc0c2 nvlddmkm+0x9a879在內核中調用Zwxxx函數,它會經過一系列復雜過程,最終調用到對應的Ntxxx函數。重要的一點是,調用Zwxxx函數會把當前線程的Previous Mode設置成Kernel Mode(參考文章:OSR)。
一個在內核中執行的線程,它既可能是從用戶程序下來的,也可能是一個一直在內核中運行的系統線程。為了區分這種情況,線程結構體中保存了一個變量,保存線程此前的Mode(Previous Mode)。對于一個從用戶層調下來的線程,它的Previous Mode是User Mode。但如果它調用了哪怕一次Zwxxx函數,其Previous Mode將被改成Kernel Mode,好像它再一次陷入了內核(從內核陷入內核)。
在這個例子中,對ZwQueryValueKey的調用,很可能影響到接下來NtQueryValueKey中調用ObReferenceObjectByHandle時的輸入參數。所以,在驅動程序中調用Native API,使用Ntxxx函數比Zwxxx函數更穩妥。
這些內容比較隱晦,涉及很多未文檔內容。我不確定。但我懷疑:如果NV驅動把調用ZwQueryValueKey的代碼改成直接調用NTQueryValueKey,可能就會解決問題。
其它
在Windbg分析完之后,我看了一下我當前使用的NV驅動版本是331.82,日期為2013年11月,大約兩個月前更新的,也算是比較新。我立刻到NV的官方網站上查看和我顯卡匹配的最新驅動(GTX 670M),有2014年1月份的最新WHQL版本:332.21。我見此便立刻下載了。我還是有點小膽怯的,所以沒再去幫助NV驗證最新的驅動是否已經解決了這個問題。如果有NV的Driver工程師看到我這篇文章,可以試一試。我保留了dump文件,需要時也可以向我索取。
總結
以上是生活随笔為你收集整理的虫趣:当NV显卡驱动碰上Verifier的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 低轨通信卫星: 开启 6G 通信时代,带
- 下一篇: 打包发布firefox的主题