虫趣:当NV显卡驱动碰上Verifier

引用注明>> 【作者:张佩】【原文:www.YiiYee.cn/blog

今天开电脑的时候,刚完成用户登陆,就遇到一个蓝屏。桌面还没有进去呢。趁着系统正处于抓取dump文件的过程中,赶紧拍了一张照,留作纪念。造成蓝屏的不是别人,乃是负责图形渲染和显示的显卡驱动:Nvidia显卡驱动。

我使用的系统:Win Blue x64。

虫趣:当NV显卡驱动碰上Verifier

事出有因

显卡驱动负责桌面系统的渲染和显示,其重要性不是一点点。所以轻易是不可能蓝屏的。我刚开始也有点纳闷,想自己昨天究竟做了什么,使得自己一大早的就遇到个天雷滚滚从天降——开机蓝屏。后来看了系统显示的错误原因,才明白过来。从上图中看到,这是在Verifier开启的前提下,诱发出来的NV显卡驱动的癫痫病。

Verifier为啥会开启呢想起来了,这确实事出有因。可追溯到我昨天看的一篇介绍数字签名的文章,它介绍了sigverif.exe这个工具,可以检测系统中已安装而未被数字签名的驱动程序。在运行了这个工具后,我很欣喜地发现系统中所有驱动程序都是签过名的。

其实这个结果解释了我的一个疑惑。因为我记得自己前段时间在运行Verifier的时候,它总是能找到两个未签名的驱动程序。其中一个是我安装的VClone虚拟光驱软件附带的内核驱动程序。从VClone的官方文档来看,它是有数字签名的,设备管理器中也有正确的显示。为什么Verifier把它列为嫌犯,我对此一直都很疑惑。

现在有了sigverif作为对照,我又再次运行了Verifier。选择验证未签名的驱动程序,果然还是有两个被列了出来。如下图所示。

虫趣:当NV显卡驱动碰上Verifier

就这个情况,我研究了半天,没有一个结论。但过程中,我出现了一个小小的操作失误。在选择驱动程序进行验证的时候,我选择了一个不可逆的验证:自动选择这台计算机上安装的所有驱动程序。

我选择这一项的初衷,是要看看verifier检索到的驱动列表,和Sigverif检索的驱动列表的区别。不料这个过程竟然是不可逆的,即使我退出后,再次选择“删除现有设置”,也已经没有用。

但当时,我却没这么觉得。我以为通过一些动作,已经把Verifier设置都清空了。其实却不然呢。这正是发生今天这个问题的初始缘由了。

自我救赎

重启后,我又试了两次,希冀可以登录到桌面后快速地关掉Verifier。但事实却很无情,我又多遇到了两次迅捷无比的蓝屏。所以我就进入到安全模式。Windows在安全模式下不使用IHV的显示驱动,而是加载微软自己的display only显示驱动

这一次我是安全的。安全模式救了我。我运行verifier,并在此选择“删除现有设置”项。在提示重启出现的时候,我服从并重启。重启到正常的系统,这次已无问题了。

调试分析

活过来后,我第一个启动的是Windbg,并加载dump文件。错误类型DRIVER_VERIFIER_DETECTED_VIOLATION对应的BSOD号是0xC4,自动分析结果如下:

自动分析的结果非常重要。它的第一个参数指明了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

这个进程rundll32是一个执行dll调用的通用的宿主进程,所以它的父进程比较能说明问题。我期望它的父进程是NV相关的进程,但最后发现CID为0xc20的进程为桌面进程。可能的情况是桌面进程调用了D3D的相关功能,进入NV显卡驱动并爆发了问题。在进入内核驱动时,用户程序传入了一个句柄参数,这个句柄指向一个和NV显卡相关的注册表键而,内核不正确地使用了这个句柄并导致问题。这个相关的注册表键值的路径为:REGISTRYMACHINESYSTEMCONTROLSET001CONTROLCLASS{4D36E968-E325-11CE-BFC1-08002BE10318} 000NVSPCAPS

逆推代码错误

根据上面的分析内容,已能很轻松地指症了。从它的调用栈上可以看出来,在进入Verifier检测函数前,系统调用的函数是ObReferenceObjectByHandle。我们看这个函数的声明:

关于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的时候,指示的AccessmodeKernelMode,就会在Verifier检验函数中产生一个类型为0xC4/0xF6的BSOD。这也是一个比较合乎情理的错误原因。

到这里问题到还没有结束,因为ObReferenceObjectByHandle是被间接调用的,NV驱动直接调用的函数是ZwQueryValueKey(它没有AccessMode这个参数)。为什么是ZwQueryValueKey呢涉及到Zwxxx和Ntxxx两组系统API的区别。见下面这一段stack。

在内核中调用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文件,需要时也可以向我索取。

来源:张佩

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2014年3月6日
下一篇 2014年3月7日

相关推荐