距离Windows大范围蓝屏事件,已经过去了6天。这6天来,国内外技术网站仍对此事热议不断,“罪魁祸首”CrowdStrike的名字被频繁提及,与之伴随的无一不是质疑和谴责:

CrowdStrike引发的系统故障导致数千架航班停飞、医院瘫痪、支付系统崩溃,被专家称为史上最大的IT故障。

据Parametrix保险公司称,CrowdStrike错误更新引发的全球技术中断,使美国财富500强企业(不包括微软)面临54亿美元的经济损失全球经济损失总额可能达到150亿美元左右

基于此,本周CrowdStrike的股价已迅速暴跌超20%。出于对引发此次故障的歉意,据悉昨日CrowdStrike还向其合作方均提供了一张价值10美元的UberEats礼品卡作为道歉:“为了表达我们的歉意,你的下一杯咖啡或夜宵由我们请客!”不过,有收到该礼品卡的用户表示,他们去兑换时,页面提示称礼品卡“已被发行方取消,不再有效”

除了以上聚焦于CrowdStrike本身的关注和报道,近日还有一个话题也在开发者圈内引起了不小的讨论:”如果CrowdStrike改用Rust的话,全球850万PC是不是就不会蓝屏了?

我惊讶地发现,过去几天发生的所有事情都是由nullderef这样简单的错误引起的。数十年来,业界一直在使用C++,所有的工具、linters、sanitizers、测试和同行评审都不足以避免这种情况的发生。因此我在想,如果改用Rust,情况是否会大不一样?

不仅如此,微软Azure部门CTOMarkRussinovich也在事发后转了一条他发布于2022年的推文:“说到语言,现在是时候停止用C/C++启动任何新项目了,请在需要使用非GC语言的情况下使用Rust。为了安全性和可靠性,业界应该宣布这些语言已被淘汰。”


眼看着不少Rust狂热爱好者开始放话“没错,Rust就是唯一答案”,一位同样喜欢Rust的资深软件工程师JulioMerino,在理智地进行了一番全盘分析后得出结论:“就算是Rust,也救不了这次CrowdStrike的中断事故。


以下为译文:

我非常喜欢Rust,也很赞同不应继续使用C++这类内存不安全的编程语言,但我还是要说:那些声称用Rust就可以避免上周五全球大面积网络中断的说法太夸张了,对Rust的口碑有害无益。

如果CrowdStrike是用Rust编写的,那确实可以降低发生故障的可能性,但它并不能解决导致故障发生的根本原因。所以看到许多人说Rust是解决这次事故的唯一答案,我就感到非常恼火——这种说法,不仅无法推动Rust的普及,反而会招来反感:C++专家们都知道本次事故的根本原因,看到这种误导性说法必然不快,从而导致系统编程世界的进一步分裂。

那么,为什么说Rust不能解决这个问题呢?接下来我会试着回答这个问题,同时也深入探讨一下造成这次故障的原因。

故障分析

以下是来自CrowdStrike官方的“事后分析”:

在2024年7月19日04:09UTC,作为持续运营的一部分,CrowdStrike向Windows系统发布了传感器配置更新。传感器配置更新是Falcon平台保护机制的持续组成部分。此配置更新触发了逻辑错误,导致受影响的系统崩溃和蓝屏(BSOD)

导致系统崩溃的传感器配置更新,已于2024年7月19日05:27UTC得到修复。

把上面这段话翻译为“人话”,就是:

1、CrowdStrike公司推送了一项配置更新

2、该更新触发了“Falcon平台”中的一个潜在bug

3、Falcon中的这个bug导致了Windows崩溃

前两点并不奇怪:对于任何在线系统来说,变更配置都是“家常便饭”,而这些更新引发代码中的bug也是常见现象。事实上,大多数宕机事件都是由人为配置变更造成的。

显然,我们应该问问为什么这个bug会存在,以及如何修复它以提高产品的稳定性。但我们别忘了第三点:为什么这个bug能够导致整台机器瘫痪?更重要的是,为什么这个bug会让全球如此多的系统宕机?

内存错误

让我们从第一个问题开始:Falcon中的bug是什么性质的?

很简单:在“ChannelFiles”(又称配置文件)解析器中存在一个逻辑错误,当遇到一些无效输入时,这段代码会试图访问一个无效的内存位置。具体细节并不重要:可能是取消引用空指针,也可能是一般保护故障等等。关键在于:崩溃是由无效内存访问问题引发的。

这时,一些Rust狂热粉可能会跳出来说::“看啊,果然!如果代码是用Rust写的,这个bug就不会存在!”我无法否认这个说法:如果用Rust,这个特定的bug确实不会出现。

但那又怎样?就算避免了这种类型的bug,下一次遇到Rust也无法避免的bug时,该宕机还是会宕机——无视Falcon的本质问题、只关注内存错误的行为,好比“只见树木,不见森林”。

那么,Falcon究竟是什么呢?

内核崩溃

在我看来,Falcon是一种“恶意软件......不过是好人的恶意软件”,也就是一个终端安全系统。Falcon通常安装在企业机器上,以便安全团队能够实时检测并解除威胁(同时监控员工的行为)。这确实有一定价值:大多数网络攻击都是通过社会工程学手段从入侵企业机器开始的。

这种类型的产品必须对机器有控制权,它必须能够拦截所有用户的文件和网络操作以扫描其内容,并且还必须是防篡改的,以防“精明”的企业用户在阅读到一些网上修复WiFi的可疑指导后尝试禁用它,以避免提交IT工单。

如何实现像Falcon这样的产品?最简单的方法,也是Windows鼓励的方法,就是编写一个内核模块。很明显,Falcon是一个内核模块,因此它运行在内核空间。这就意味着,Falcon代码中的任何错误都可能破坏正在运行的内核,进而导致整个系统崩溃

我所说的“任何错误”,是真的。内核不仅会因为内存错误而崩溃,也不一定非要“内核崩溃”才能让机器无法使用:死锁会让阻止内核前进,系统调用处理程序中的逻辑错误会阻止用户空间之后打开任何文件,一个无限递归算法会耗尽内核的堆栈……破坏内核稳定性的方法实在是太多了,所以我说就算是Rust也不能完全避免这种事故的发生。

Rust的内存安全性只能解决一种类型的崩溃。另外,Rust生态系统中对正确性的关注也确实可以最大限度地减少其他类型逻辑错误的出现。但是……虽然我们都希望做到完美,但也必须接受错误会发生的事实——断言Rust是解决问题的唯一答案和坚持使用C++一样,都是不负责任的行为

要知道,在内核空间工作的C++开发者,要比了解内核内部结构的Rust开发者多得多。因此,大部分C++开发者都知道这种说法的可笑之处,同时也会增加两个社区之间的敌意,更是完全违背了让人们转向安全语言的这个目标。Rust开发者知道Rust确实可以改善现状,但C++开发者无法接受,因为他们听到的观点无法引起他们的共鸣。

从内核空间到用户空间

还有人说,如果Falcon不在内核中运行,就根本不会发生这种情况。嗯,这个说法要好一点,但……仅此一点也不一定就能解决问题

正如我之前提到的,Falcon需要尽可能防篡改,防止恶意软件对其进行干扰,并防止被入侵的用户试图禁用它。如果恶意软件或人类能够轻易做到这一点,那么这个产品就毫无用处。

现在,Windows内核完全有能力禁止类似Falcon的内核模块。相反,内核可以暴露一系列API,让用户空间的应用程序能够接入这些API来提供类似的功能。你知道吗,微软确实尝试过让Windows朝这个方向发展,但杀毒软件公司威胁要以反垄断为由起诉,结果整个计划无疾而终。因此,我们现在只能忍受一个安全性较低的系统,因为杀毒软件公司需要销售那些烦人的产品。

但是,我们先暂时放下这个麻烦不谈。即使Falcon运行在用户空间,并通过受控API与内核通信……这就足以防止系统故障吗?请注意,这些API也需要防篡改。试想一下,如果你希望这个用户空间驱动程序在内核执行每个二进制文件之前进行验证,也就是让内核在每次执行时都需要从用户空间驱动程序获得答案,而这个驱动程序又有问题,那么系统将无法再执行任何程序。

可如果你让内核与驱动程序通信变成可选项,以便内核可以容忍崩溃的驱动程序,那么就等于给恶意软件开了一条路,它们可以先尝试崩溃驱动程序,然后再入侵系统

因此,仅仅“迁移到用户空间”显然也不是解决办法

部署中的漏洞

如果我们必须接受bug的存在,而内存相关的bug并不是唯一会导致系统崩溃的原因,且将驱动程序移到用户空间也不是很好的解决方案……那难道就无计可施了?真的没有办法防止这种情况发生吗?

以上我说的这些,都是可以(也应该)采取的措施,以减少系统故障发生的概率,但我们必须接受这样一个事实:这次代码bug只是特定的触发因素,就算换一个触发因素也可能会产生类似的恶果本次全球宕机事件的根本原因,在于配置变更的发布流程。

根据SRE101(或DevOps,随便你怎么叫)规定,配置变更必须分阶段进行,以缓慢和受控的方式部署,并在每个步骤进行验证。这些变更应该先在很小的范围内进行验证,然后再向全球推送,而且每次推送都应是渐进的。

考虑到Falcon的关键性以及bug可能带来的巨大影响,我很难相信CrowdStrike没有对部署进行任何验证。但根据CrowdStrike最新更新的事后分析来看,他们确实没有进行任何形式的测试或金丝雀部署(在将更改推广到整个服务集群之前,先把更改推广到一小部分用户进行测试),这实在是令人难以置信的疏忽。

所以说,CrowdStrike的部署实践是造成此次事件的罪魁祸首——也就是说,这次宕机事件是一个流程问题,而不是代码或技术问题,改用Rust也无济于事。

CrowdStrike发布初步审查报告,总结:“测试和流程不完善”

诚然如JulioMerino所说,CrowdStrike在其官网最新发布了此事件的初步审查报告,并公开了此次事件的整体时间线:

安全故障始于2月28日,当时CrowdStrike开发并分发了一个Falcon传感器更新,旨在检测一种新兴的、利用Windows命名管道的攻击技术,而传感器更新在发布前通过了常规测试。

3月5日,该更新接受了压力测试并得到验证,可以投入使用。因此,当天CrowdStrike就向使用新的恶意命名管道检测的客户分发了快速响应更新。

在4月8日-4月24日期间,CrowdStrike又推送了三次使用这种新代码模板的快速响应更新,并表示所有这些更新“在生产环境中按预期运行”。

到了7月19日,CrowdStrike又用3月份的传感器模板发布了两个快速响应更新,但这次其中一个更新推送的数据格式不正确。然而,CrowdStrike用于检查内容更新是否按预期运行的验证系统有问题,它没有发现这个要推送给所有人的配置文件存在错误。于是乎,这个本该停止发布的错误更新就造成了全球850万PC蓝屏

部分网友在看过CrowdStrike这份冗长的初步审查报告后,精辟总结:“说了这么多,就是想说我们的测试和流程不完善,不小心把垃圾发布出来了”;“字数惊人,但归根结底还是测试代码有bug以及测试不够”;“不好意思,我们对此更新进行的唯一测试,是一个没真正通过的自动化测试”。

因此对于这种事故原因,绝对不是改用Rust就能解决的,根本还是在于测试环节和部署流程的不规范。与此同时,CrowdStrike也在事后总结中表示,今后要在发布更新前增加软件测试,并逐步推出更新,具体补救措施大体分为三个部分:

1、软件弹性和测试

(1)通过使用以下测试类型改进快速响应内容测试:本地开发人员测试,内容更新和回滚测试,压力测试、模糊测试和故障注入,稳定性测试和内容接口测试;

(2)在快速反应内容的验证器中增加额外的验证检查;

(3)增强内容解释器中现有的错误处理功能。

2、快速响应内容部署

(1)对快速响应内容实施交错部署策略,从金丝雀部署开始,再逐步将更新部署到更大的区域;

(2)改进对传感器和系统性能的监控,在快速响应内容部署期间收集反馈信息,以指导分阶段部署;

(3)允许用户选择部署的时间和位置,使其能够更好地控制快速响应内容更新的交付;

(4)通过客户可订阅的发布说明提供内容更新详情。

3、第三方验证

(1)进行多个独立的第三方安全代码审查;

(2)对从开发到部署的端到端质量流程进行独立审查。