Raymond Chen 2007年05月04日
我对Windows XP上进程退出过程理解不足,导致一个安全补丁被迫召回
简要
Raymond Chen坦诚地分享了他在处理一个安全补丁时犯下的错误,以及这个错误如何导致需要召回和重新发布补丁。他详细说明了问题的原因,包括COM的退出过程和DLL的工作线程处理不当,以及这些问题如何导致了系统挂起。此外,他还提到了召回过程的一些细节,并以幽默的方式欢迎Slashdot的读者参与讨论。
正文
去年,一个Windows安全更新因为导致一些机器挂起而受到很多批评,这是我的错。(这使得我之前在财务分析师会议上搞砸一个演示的错误看起来像小菜一碟。)
这个安全修复针对的攻击类别是,人们可以构造快捷方式文件或其他项目,这些项目指定了一个从未打算用作外壳扩展的CLSID。正如我们之前看到的,很多人在IUnknown::QueryInterface
上犯了错误,如果你传递了这些有缺陷实现的CLSID之一,资源管理器(Explorer)会尽职尽责地创建它并尝试使用它,然后坏事就发生了。该对象可能会崩溃、挂起,甚至可能会损坏内存并继续运行(有点)。
为了保护免受有缺陷的外壳扩展的影响,资源管理器被修改为使用一个名为verclsid.exe
的辅助程序,其工作是充当“小白鼠”,承载外壳扩展,并进行一些初步的侦查,以确保在允许外壳扩展在资源管理器中自由运行之前,它通过了一些基本的功能测试。这样,如果外壳扩展发疯了,受害者将是verclsid.exe
进程,而不是主资源管理器进程。
verclsid.exe
程序创建了一个监视线程:如果初步侦查花费了太长时间,监视线程会假设外壳扩展挂起了,并向资源管理器报告,“不要使用这个外壳扩展。”
我是被引入研究这种新行为的人之一,对其设计进行测试,对其实现进行测试,审查每一行更改的代码,并确保它确实做到了它应该做的事情,而不是在过程中引入任何新的错误。我们发现了一些问题,测试人员发现了其他一些问题,而与此同时,时间在流逝,因为这是一个安全补丁,人们喜欢嘲笑微软需要多长时间才能凑出一个安全补丁。
补丁发布了,然后报告开始进来,机器挂起了。怎么可能呢?我们特意创建了一个监视线程来捕捉挂起的外壳扩展;为什么监视线程没有做好它的工作?
这是今天课程的长篇铺垫。
在运行完其合理性测试后,verclsid.exe
程序释放了外壳扩展,未初始化COM,然后调用了一个特殊退出代码的ExitProcess
,该代码意味着“所有测试已通过”。如果你读了昨天的文章,你已经知道我搞砸了哪里。
实现外壳扩展的DLL创建了一个工作线程,因此它对自己进行了额外的LoadLibrary
,以防止在COM将其作为CoUninitialize
拆除的一部分释放时被卸载。当DLL得到其DLL_PROCESS_DETACH
时,它通过设置一个“现在清理”事件(工作线程监听该事件)的常见技术来关闭其工作线程,然后等待工作线程用一个“好的,我全做完了”事件回应。
但回想一下,进程退出的第一阶段是终止除调用ExitProcess
的线程之外的所有线程。这意味着DLL的工作线程不再存在。在设置事件以告诉(不存在的)线程进行清理之后,它接着等待(不存在的)线程说它完成了。由于周围没有人监听清理事件,因此“全做完了”事件从未被设置。DLL在其DLL_PROCESS_DETACH
中挂起了。
为什么我们的监视线程没有拯救我们?因为监视线程也被杀死了!
现在,所有这一切的根本原因是一个有缺陷的外壳扩展在其DLL_PROCESS_DETACH
中做了坏事,但责怪外壳扩展错过了重点。毕竟,正是存在有缺陷的外壳扩展这一事实,才首先创造了对verclsid.exe
程序的需求。
欢迎Slashdot的读者。由于你不会在发布自己的评论之前阅读现有评论,我会在这里提出一些更有意义的评论。
有缺陷的外壳扩展是随一个不再生产的打印机驱动程序一起包含的。在你测试套件中找到其中一个的运气不错。
安全更新被召回并重新发布,这是一个单一的动作,大多数人称之为更新或刷新,但在标题中使用召回一词效果更好。