架构师大咖
架构师大咖,打造有价值的架构师交流平台。分享架构师干货、教程、课程、资讯。架构师大咖,每日推送。
公众号
该公众号已被封禁
距离 Windows 大范围蓝屏事件,已经过去了 6 天。
这 6 天来,国内外技术网站仍对此事热议不断,“罪魁祸首” CrowdStrike 的名字被频繁提及,与之伴随的无一不是质疑和谴责:
CrowdStrike 引发的系统故障导致数千架航班停飞、医院瘫痪、支付系统崩溃,被专家称为史上最大的 IT 故障。
据 Parametrix 保险公司称,CrowdStrike 错误更新引发的全球技术中断,使美国财富 500 强企业(不包括微软)面临 54 亿美元的经济损失,全球经济损失总额可能达到 150 亿美元左右。
基于此,本周 CrowdStrike 的股价已迅速暴跌超 20%。出于对引发此次故障的歉意,据悉昨日 CrowdStrike 还向其合作方均提供了一张价值 10 美元的 Uber Eats 礼品卡作为道歉:“为了表达我们的歉意,你的下一杯咖啡或夜宵由我们请客!”不过,有收到该礼品卡的用户表示,他们去兑换时,页面提示称该礼品卡“已被发行方取消,不再有效”。
除了以上聚焦于 CrowdStrike 本身的关注和报道,近日还有一个话题也在开发者圈内引起了不小的讨论:”如果 CrowdStrike 改用 Rust 的话,全球 850 万 PC 是不是就不会蓝屏了?“
我惊讶地发现,过去几天发生的所有事情都是由 null deref 这样简单的错误引起的。数十年来,业界一直在使用 C++,所有的工具、linters、sanitizers、测试和同行评审都不足以避免这种情况的发生。因此我在想,如果改用 Rust,情况是否会大不一样?
不仅如此,微软 Azure 部门 CTO Mark Russinovich 也在事发后转了一条他 发布于 2022 年的推文:“说到语言,现在是时候停止用 C/C++ 启动任何新项目了,请在需要使用非 GC 语言的情况下使用 Rust。为了安全性和可靠性,业界应该宣布这些语言已被淘汰。”
眼看着不少 Rust 狂热爱好者开始放话“没错,Rust 就是唯一答案”,一位同样喜欢 Rust 的资深软件工程师 Julio Merino,在理智地进行了一番全盘分析后得出结论:“就算是 Rust,也救不了这次 CrowdStrike 的中断事故。”
以下为译文:
我非常喜欢 Rust,也很赞同不应继续使用 C++ 这类内存不安全的编程语言,但我还是要说:那些声称用 Rust 就可以避免上周五全球大面积网络中断的说法太夸张了,对 Rust 的口碑有害无益。
如果 CrowdStrike 是用 Rust 编写的,那确实可以降低发生故障的可能性,但它并不能解决导致故障发生的根本原因。所以看到许多人说 Rust 是解决这次事故的唯一答案,我就感到非常恼火——这种说法,不仅无法推动 Rust 的普及,反而会招来反感:C++ 专家们都知道本次事故的根本原因,看到这种误导性说法必然不快,从而导致系统编程世界的进一步分裂。
那么,为什么说 Rust 不能解决这个问题呢?接下来我会试着回答这个问题,同时也深入探讨一下造成这次故障的原因。
故障分析
以下是来自 CrowdStrike 官方的“事后分析”:
在 2024 年 7 月 19 日 04:09 UTC,作为持续运营的一部分,CrowdStrike 向 Windows 系统发布了传感器配置更新。传感器配置更新是 Falcon 平台保护机制的持续组成部分。此配置更新触发了逻辑错误,导致受影响的系统崩溃和蓝屏(BSOD)
导致系统崩溃的传感器配置更新,已于 2024 年 7 月 19 日 05:27 UTC 得到修复。
把上面这段话翻译为“人话”,就是:
1、CrowdStrike 公司推送了一项配置更新。
2、该更新触发了“Falcon 平台”中的一个潜在 bug。
3、Falcon 中的这个 bug 导致了 Windows 崩溃。
前两点并不奇怪:对于任何在线系统来说,变更配置都是“家常便饭”,而这些更新引发代码中的 bug 也是常见现象。事实上,大多数宕机事件都是由人为配置变更造成的。
显然,我们应该问问为什么这个 bug 会存在,以及如何修复它以提高产品的稳定性。但我们别忘了第三点:为什么这个 bug 能够导致整台机器瘫痪?更重要的是,为什么这个 bug 会让全球如此多的系统宕机?
内存错误
让我们从第一个问题开始:Falcon 中的 bug 是什么性质的?
很简单:在“Channel Files”(又称配置文件)解析器中存在一个逻辑错误,当遇到一些无效输入时,这段代码会试图访问一个无效的内存位置。具体细节并不重要:可能是取消引用空指针,也可能是一般保护故障等等。关键在于:崩溃是由无效内存访问问题引发的。
这时,一些 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 只是特定的触发因素,就算换一个触发因素也可能会产生类似的恶果。本次全球宕机事件的根本原因,在于配置变更的发布流程。
根据 SRE 101(或 DevOps,随便你怎么叫)规定,配置变更必须分阶段进行,以缓慢和受控的方式部署,并在每个步骤进行验证。这些变更应该先在很小的范围内进行验证,然后再向全球推送,而且每次推送都应是渐进的。
考虑到 Falcon 的关键性以及 bug 可能带来的巨大影响,我很难相信 CrowdStrike 没有对部署进行任何验证。但根据 CrowdStrike 最新更新的事后分析来看,他们确实没有进行任何形式的测试或金丝雀部署(在将更改推广到整个服务集群之前,先把更改推广到一小部分用户进行测试),这实在是令人难以置信的疏忽。
所以说,CrowdStrike 的部署实践是造成此次事件的罪魁祸首——也就是说,这次宕机事件是一个流程问题,而不是代码或技术问题,改用 Rust 也无济于事。
CrowdStrike 发布初步审查报告,总结:“测试和流程不完善”
诚然如 Julio Merino 所说,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)对从开发到部署的端到端质量流程进行独立审查。
Python入门到精通
Python入门到精通:人生苦短,我用Python!Python每日推送、Python教程、Python资料、Python视频、Python项目、Python学习等。
公众号
该公众号已被封禁