老万> 深度吐槽CrowdStrike事故报告
三个星期前的星期五(2024年7月19日),电脑安全公司 CrowdStrike 在更新防黑客软件 Falcon 的配置数据时捅了一个大篓子,导致全球超过 850多万台 Windows 电脑崩溃,用户祭出重启大法也无济于事。
这一事故涉及大批机场、学校、医院、超市。公司等客户。《财富》(Fortune)杂志估计事故给财富 500 强公司造成了 54 亿美元经济损失,给其它公司造成的损失和次生伤害更是罄竹难书。
5 只独角兽啊,咣当一下就给干没了。
CrowdStrike 自己更惨,事发之后,股价跌了 20% 多,市值蒸发了 150 亿美元。
事故发生后,CrowdStrike 发布了一系列简短声明,对事故原因做了大致的解释,但没有透露太多细节,只是说在调查完毕之后会公布结果。
上周他们公布了一个初始报告。这个星期二,正式的报告来了。
在这瞬息万变的世界里,群众对任何事情都只有五分钟的关注(谁还记得姜萍?)。与事故发生时铺天盖地的报道比,这一次CrowdStrike的声明可谓波澜不惊。显然,热度已经迅速消退。
然而,对 IT 从业人员来说这次灾难是一个千载难逢的富矿,不容错过。
不要以为这样的事只会发生在不靠谱的公司。CrowdStrike 犯过的错,我们很多人也犯过。不同的是,我们的运气比较好,没有在屋漏的时候遇上连夜雨。
这份事故报告值得细细品,慢慢读。通过了解 CrowdStrike 的巨坑是怎样挖成的,我们可以顾影自怜以史为鉴,避免成为下一个万人唾骂的对象。
~~~~
CrowdStrike 的报告全文很短,不过 12 页,可以很快看完。
在此之前,微软负责企业和操作系统安全的副总裁 David Weston 已经在微软的安全博客上发文《Windows安全工具最佳实践》(Windows Security best practices for integrating and managing security tools),委婉指出 CrowdStrike 这次事故的内情。
把双方的声明放在一起看很有意思。我们可以学习到如何文明甩锅和不带脏字吐槽猪队友。
比如,这次事故之所以后果如此严重,是因为出错的软件以内核模式运行,享有最高特权。要是出错的是用户模式的软件,绝不至于让系统整个宕掉。对此微软义正辞严地指出:
(解读:兄弟姐妹们呀,不是我家 Windows 不安全啊,是 CrowdStrike 没用好啊!明明我们有那么多方法在用户模式下实现安全功能,他们就是不用啊!)
CrowdStrike 则深情回应:
(解读:老乡们啊,别听微软画饼啊!那当不了吃的啊!现在而今眼目下内核模式还是要的哈!)
两大美国甩锅高手尖峰对决
在解读 CrowdStrike 事故报告之前,先更正一下我上一篇文章《如何写出万人唾骂的软件 - 史上最大Windows蓝屏事故分析》中的一些错误和不准确的地方:
中国躲过一劫,根本原因不是中国对系统国产化的推进,而是因为CrowdStrike不对中国用户提供服务。
造成事故的更新没有经过 Windows 的更新发布平台,是 CrowdStrike 自己的行为。而Windows 自身的更新是给了用户选择权的。(但这不代表微软没有监管不力的责任。)
微软把系统驱动的核心权限开放给 CrowdStrike 这样的第三方,是因为自己跟欧盟有一个反垄断协议。微软也不想,微软很委屈。
事故的直接导因不是空指针访问,而是数组越界造成的垃圾指针访问。
好了,开讲。
~~~~
在介绍事故细节前,我们先把事情的背景和来龙去脉大致理一遍,让大家有个直观的理解。
首先,CrowdStrike 是做什么的?
它是一家总部位于美国德州的电脑安全公司,成立于 2011 年,为电脑提供防病毒防黑客服务。你就当它是美国的 360 安全卫士吧。这次出事的是它的 Falcon 安全软件的 Windows 版本。
CrowdStrike 跟微软又是什么关系?
前者提供的安全服务是对后者操作系统的补充。我们知道 Windows 自带微软防卫者(Microsoft Defender)防毒软件,提供了基本的安全功能。CrowdStrike 的卖点是它比微软“更懂安全”(类似于百度比谷歌“更懂中文”)。
Falcon 是一款云集成度非常高的产品,由 CrowdStrike 的服务器和运行在用户机器上的防毒软件协同工作。
Falcon 客户端分两大块:逻辑部分和数据部分。
负责逻辑的部分叫“传感器”(sensor),取这名的意思是大兄弟在看着你,系统出了啥幺蛾子它都知道。
数据部分就是传感器配置文件,又叫通道文件。这些文件定义了各种检测黑客攻击的规则。
传感器的代码分两大模块:配置内容解释器和检测引擎。前者负责解析配置文件,后者负责执行配置文件中的规则。它们都有光明的未来最高级别的特权。
CrowdStrike Falcon 安全软件架构
传感器之所以有特权,主要是因为从理论上说这样可以让防御更可靠:它可以缴病毒的枪,而不是被病毒缴了枪。
传感器做的就是检测系统动态,再按配置文件中定义的规则对检测到的信息做出反应。比如发现一些程序在干不该干的事,就会报警并杀死这个运行中的程序。
用户端的 Falcon 软件每天都会接收服务器发来的指令,更新配置文件。
这种设计是因为电脑安全是一场信息不对等的攻防游戏,用户在明处,黑客在暗处。新的攻击手段层出不穷,守方不得不活到老,学到老(但也可能像 7/19 事件里一样学废了)。
每当 CrowdStrike 有了新的发现和改进,就会向用户发送配置更新,好挫败最新的攻击技术(结果这一次真的好挫)。
注意:频繁更新的是配置文件,不是传感器软件本身。因为传感器有特权,微软对它的发布和更新有貌似严格的控制(下面会解释为什么说是“貌似”),以免它有意无意地干坏事。
每次更新传感器软件,都必须通过微软的测试和认证,少则几天,多则几个月,等不起啊。
而微软认为配置文件没那么重要,所以允许第三方软件厂商自行更新(下面我们会分析这种想法为什么是幼稚的)。于是, CrowdStrike 大部分时候都是通过更新配置文件来让用户端的软件与时俱进。
~~~~
7月19日发生了什么?
那天 04:09 UTC,CrowdStrike 为 Falcon 发布了一批新的配置文件,用于对付新的黑客攻击手段。
看起来,这不过是又一次常规操作。这种操作,CrowdStrike 平均每天都要做几次,就像呼吸一样自然。
但这一次呼吸,让更新后的系统立马断气了。
在 2024年7月19日星期五04:09 UTC 至 2024年7月19日星期五05:27 UTC 期间在线接收了更新的、运行 Falcon 软件7.11 及以上版本 的 Windows 主机全部中招。
发现事故后,CrowdStrike 在 2024年7月19日星期五05:27 UTC 撤回了肇事更新,但为时已晚:数百万电脑已经彻底瘫痪,无法和网络建立任何联系。
至此,只有一个方法能让它们起死回生:让客户手动重启机器并手动删除有问题的配置文件。
中招公司的系统管理员们热泪盈眶:等了十年,我还以为资本家根本把我忘了。想不到一张卫生纸,一个管理员都有他的用处啊。
~~~~
先前大家猜测的事故原因是 CrowdStrike 系统驱动程序出现了空指针访问(null pointer dereference)。
指针是电脑软件术语,就是某段数据在内存中的位置,你可以把它想象成门牌号。通过指针,我们就可以从内存中指定的地点读出特定的数据。
空指针是一个特殊的指针,指向一个特殊的不能访问的地址(比如“前门大街 0 号”,而前门大街根本没有 0 号),通常用来表示“数据不存在”。
空指针访问,就是软件出了逻辑错误,强行访问不能访问的地址,导致系统崩溃。
CrowdStrike 公布的事故原因和大家猜测的略有不同。倒是没有访问空指针,而是因为数组下标越界得到了一个垃圾指针,又通过这个垃圾指针访问了一个不能访问的地址。总之跟访问空指针半斤八两,结果都是系统崩溃。
数组下标越界也是一种常见的编程错误:一片数据,一共 N 个元素,放在连续的内存里,好比 N 家人住在同一条街上,门牌号相连。假比第一家是 8 号,第二家就是 9 号,第三家是 10 号,依此类推。如果到第 N + 1 家串门,就是下标越界,擅闯私宅。
CrowdStrike 的问题是:明明只有 N 个元素,代码偏偏要访问第 N + 1 个。
越界的时候,程序没有立即跑飞,还在走既定流程。
前 N 家每家都存了一个指针。第 N + 1 家还没有装修好,家里一堆垃圾。但是驱动程序不管,把它家里的垃圾当成指针,一看,上面写的是“后海胡同 9527 号”。
这程序倒也负责,先检查了这个地址是不是空指针。发现不是,便放心去后海胡同 9527 取货。
可地图上找不到这个地址。
于是 Windows 知道坏了 - 掌握最高权限的系统驱动程序出现了无厘头行为,除了挥泪自尽别无他法。否则接下来不知还会出现啥离谱的事。
~~~~
那么下标越界又是怎么发生的呢?这就要从传感器配置文件的内容讲起了。
每个配置文件都记录了一堆规则。每条规则定义了如何发现和处理某种攻击。比如:“要是进程试图删除根目录,就杀死进程”。
规则本身不长眼睛,看不到系统的监控数据。那它如何能发现攻击呢?没关系,检测引擎是 Falcon 的眼睛,会把它观测到的系统状态以参数(parameter)的形式传给配置内容解释器,解释器就可以执行规则了。
出于某种原因,CrowdStrike 把规则不叫规则,叫模版(template)。比如,模版类型(template type)定义了规则的格式(比如,有几个参数,每个参数都是什么意思),模版实例(template instance)则是一条具体的规则。每条规则都必须遵循一个事先规定的格式,否则传感器就不知道如何解读它。
学过编程的同学应该已经看出来了:模版类型和模版实例的关系,就是类型和变量的关系。每个变量都必须有一个类型。
配置文件内容
在上面的例子里,配置文件里放了 5 个模版实例,前三个的类型是 A,后两个的类型是 B。
这次事故的根本原因,是传感器内部对模版类型的格式理解出现了分歧。
具体地说,7/19 的配置文件更新引入了两个新的 A 型模版。Falcon 传感器在得到新的配置文件后马不停蹄地开始解释执行其中的模版。
检测引擎以为 A 型模版有 20 个参数,便整理好 20 个参数传给解释器。而 A 型模版其实有 21 个参数。解释器出于对检测引擎的一万个信任(都是自家人,你办事,我放心!),也就没有检查一下自己得到的参数到底是不是 21 个。
这两个模块都该打屁股:检测引擎把参数给错了,解释器在执行一条规则前没有检查前提条件。但凡任何一个模块把自己的工作做到位了,系统都不会崩溃。
阿呆和阿傻,半斤八两
其实,这两个逻辑错误从几个月以前就存在了,只是一直没有哪个 A 型模版用到第 21 个参数,所以一直没有触发。7月19日,爆雷了。
~~~~
问题又来了:这么重要的系统软件,按说在发布前都要经过严格的测试。这么明显的臭虫是如何从层层测试中杀出重围的呢?
其实要分成两个问题:
第一,包含这些逻辑错误的传感器软件是如何通过微软的“严格”认证的?
第二,触发这些错误的数据(新的配置文件)是如何通过 CrowdStrike 的测试的?
先说第一个。
每次 Falcon 升级确实都要经过微软的 Windows Hardware Quality Labs(WHQL)认证。这个认证说起来好生厉害,基本上你能想到的各种手段都用上了:功能测试,寿命测试,压力测试(包括故障注入),模糊测试,性能测试,静态分析,等等等等。能通过这些考验,那还不得是人中吕布马中赤兔啊?
然而,这一系列令人眼花缭乱的操作并没有抓住这只臭虫,因为:在全部这些测试时使用的都是当时已经发布的配置文件。
这就有点搞笑了。我们知道,Falcon 系统的行为是受三大因素影响的:软件代码,配置文件,和系统监测数据。任何一个因素变了,结果都可能不同。所以,在认证某个版本的传感器软件的时候,我们必须试图证明无论配置文件和监测数据如何变化,系统都会屹立不倒。(划重点:关键词是“无论......都会......”)
然而,微软仅仅看到 Falcon 系统在配合当前配置文件使用时工作正常就盖章放行,实在是神经过于大条了。
再去看那些花费在 WHQL 认证上的大量人力物力,shit 上雕花的即视感扑面而来。
测试好是好,但从其本质上来说,测试只能证明系统有错,不能证明系统无错。更多的测试可以提升我们对系统正确性的信心,但要是测试的策略错了,卷死也无益。
再看第二个问题:有问题的配置文件又是如何通过 CrowdStrike 测试的?
因为配置文件要求一日三更,对它的测试力度不可能和 WHQL 一样。即便如此,像这种开机即崩的大臭虫没有被测试发现也是匪夷所思。
首先,这说明 CrowdStrike 没有做端到端(end-to-end)测试,最多只做了单元(unit)测试和集成(integration)测试。
单元测试,测的是某个模块在跟外界隔离的情况下是否能正常工作:如果输入是 ABC,输出是不是 EFG?
集成测试,测的是两个或多个模块组合在一起能否正常协同工作。它能揪出一些单元测试暴露不了的问题,比如一个模块向另一个模块传错了参数。
端到端测试,测的是一个真实系统中全部模块在一起是否正常工作。它又能发现一些在集成测试时难以发现的问题。这次要是有端到端测试,肯定不会让有错的配置文件通过。
从单元到集成再到端到端,维护和运行测试的成本越来越高,所以通常建议这三类测试的占比是大致 70 比 20 比 10。端到端测试可以少一点,但不能没有,尤其是像安全软件这样极端重要的系统。
三类测试的推荐占比
其次,其实 CrowdStrike 的测试曾经发现了问题,但他们没有重视,等到爆雷了才后悔莫及,尘世间最痛苦的事莫过于此。
7/19 发布的模版实例有两个有问题。它们中的一个因为测试系统内容验证器(Content Validator)自身的一个臭虫顺利通过了。幸运的是,另一个有问题的实例被内容验证器拦下了。
此时,如果负责发布的工程师认真对待这一警报,仔细分析其原因,完全来得及把故障消灭在襁褓之中。
然而,不可思议的事发生了!
这位工程师基于以下几点判断,毅然决然强行发布了更新:
“类似的”模版实例(其实有很大不同)从三月份起就开始用,一直没出过问题。
内容验证器包括很多检查,它们大都通过了,除了这一个,“应该问题不大”。
更新配置文件以前成功做过无数次了都没事,“这次也不会有事”。
迷之自信,莫过于此。临时工,一定是临时工!
~~~~
在事故报告中,CrowdStrike 提出了一系列亡羊补牢措施。其中大多数已经在笔者上一篇文章中先行提出了,总结一下:
立即发布补丁修正代码中的逻辑错误。
强化、完善测试案例和测试逻辑,确保每个新的模板实例在发布前都经过完备测试。
分阶段发布更新:先少少地推给一部分用户,确认没问题再推给更多人。
把更新控制权交给用户,允许他们选择配置文件更新的策略和时间。
与独立的第三方软件安全供应商合作,对 Falcon 传感器代码和端到端质量过程做进一步审查。
这些做法,不是不好,而是很好,但是还不够好。
咋说呢?他们这些整改措施吧,虽然都对提升系统可靠性有所帮助,但思路还是聚焦在如何尽可能尽早发现代码中的错误上,寄希望于更多的测试,更完善的流程,将发生错误的几率降低再降低。
能不能再解放一下思想,从源头上杜绝这类安全隐患?
打个比方,现在的做法好比做一只木桶,要求不能漏水。他们的解决方案是买更好的放大镜,对着成品仔细端详,寻找每一个可能漏水的地方。同时训练员工不能打马虎眼。
这样做好是好的,但是治标不治本。
更好的方法是从材质上着手,降维打击。木头天生就有封闭不严的问题,那就不用木头好了,用铝合金一次浇铸成型。只要保证铝桶有一定的厚度,那么可以相信它是不会漏水的。
如何对安全隐患降维打击?用形式化方法(formal methods)来开发、验证系统,严格证明系统不会有此类安全问题。
具体地说,包括:
用内存安全的语言(比如 Rust)取代 C++。
要求程序员提供代码安全性的证明(比如通过对核心代码添加标注(annotations)),并自动检查证明的正确性。
这些是老万三个星期前就提过的,但 CrowdStrike 并没有认识到它们的重要性。
那么什么是形式化方法?为什么老万对它推崇备至?
简单地说,形式化方法就是说人是靠不住的,程序的安全性不能靠程序员拍胸脯,也不能光靠测试,要有靠得住的证明。
有人问:怎么保证这个证明是正确的呢?
问得好,既然人是靠不住的,我们就不能依赖人类来验证程序正确性的证明。验证工作必须由机器按照一定的算法来机械完成。机器干别的不行,不知疲倦地执行算法还是可以的。
有人又问了:算法不是人设计的吗?如何保证这个验证证明的算法是正确的呢?是不是还得有机器来验证这个算法的正确性?这不就无穷无尽了吗?
这是很好的问题,答案是:
证明本身可能很复杂,但如果我们能够把它拆成一个一个很小的步骤,每个步骤都能用机械的方法去验证是否正确,那么我们可以用机器来验证这样的证明过程。
只要这个验证系统设计得好,我们可以做到,虽然写出一个正确的证明不容易,但是检验证明是否正确只需要很容易的机械操作。
只要这个验证系统设计得好,验证算法可以很简洁。通过计算机科学家们反复、认真的审查,我们可以事实上相信这样的算法没有错误。所以,这个验证过程不会无穷无尽地发散下去。
如果硬要抬杠,从理论上说很多科学家也可能会集体犯错。但科学从来都不是保证自己绝对正确的。如果有证据某个算法错了,我们可以修正算法。但只要这个验证算法有错的概率足够小,相信它还是比相信人靠谱。
Rust 为什么比 C++ 安全?因为 Rust 把一些证明程序正确的工作强行加给了程序员。比如,它的类型检查系统让人又爱又恨。爱的是严格的类型检查可以防止一大类臭虫的发生,恨的是程序员经常需要很多时间才能理解编译错误并找到修正办法,让人抓狂。对经验不甚丰富的程序员,Rust 的一个编译错误落在头上就可能是一座山。这样,在快速原型开发的时候,经常会让人有绑沙袋行军,用牛刀杀鸡之感。
但是涉及到电脑安全这样的事情,正是需要用牛刀的时候,就要舍得用好钢。用 Rust 开发,代价再大能大过 150 亿美元吗?
~~~~
CrowdStrike 邀请了第三方机构来审查他们的代码,这是一件好事,但这件事不能只做一次,否则日后又会故态复萌。
要从根子上改变 CrowdStrike 的开发风气,让他们的内部代码审查不是走过场,而是真正起到作用。
任何一个有经验的程序员,特别是有多年开发安全软件经验的程序员,对下标越界这样的错误都应该非常敏感。如果看见一个数组下标操作,他们的第一反应会是:这个下标会不会越界?我能证明它永远不会越界吗?
这样的反应,应该已经融化在血液里了。如果没有这样的想法,只能说明他还不是一个合格的电脑安全程序员。
忘了检查空指针跟忘了检查下标越界从本质上来说都是一类问题:没有检查一个操作的前提条件。优秀的程序员对每个操作的前提和后续条件都了如指掌。没有优秀的程序员,如何保障安全软件自身的安全?
~~~~
再对这个安全行业多说几句。
关于计算机安全软件是否需要系统内核权限这件事儿,其实并无定论。
从理论上说,要真正防住一些高端的攻击,安全软件必须具有内核权限,否则自己被人干死了还不知道。
但是对大多数普通用户来说,这样带来的风险也不可小觑,甚至可能会超过被黑客攻击的风险。7/19 就是活生生的教训。
通过这件事情大家也看出来了,对于 CrowdStrike 这样的电脑安全公司不必盲目崇拜。他们并不是天选之子,常人会犯的错他们也会犯,甚至有些错误非常低级。
其实想想就明白,计算机安全并不是当前世界发展的热点。大多数头脑灵活的程序员都一头扎入了互联网、人工智能、机器学习等来钱快的行业。这年头还在埋头干软件安全的,有的是真爱,也有的是没有更好的选择。
然而安全软件的开发其实对程序员有更高的要求。这就出现了需求和供给的矛盾:一方面安全程序员的标准应该非常高,因为他们一个小小的失误就可以让客户万劫不复。另一方面,这样的职位可能吸引不了这么多高水平的程序员。
可以想象,在实践中电脑安全公司不得不降低用人标准。这样的后果一天半天看不出来,但出来混总是要还,只是有早有晚。
滑稽的是,千千万万的用户把自身最核心的资产(信息系统)托付给了这样一个草台班子,给了他们可以先斩后奏的尚方大宝剑。他们把剑挥舞得虎虎生风,尘埃落定之后,才发现自己在裸奔。
微软说得对,安全软件开发商需要尽量减少对内核模式的依赖,尽可能把操作移到用户模式,否则这些安全软件本身就是一大隐患。
但微软自身也应该加大推进这一变革的力度,否则 Windows 系统出了问题,受损失的是微软的客户,大家第一时间想到的还是 Windows 不安全,久之这牌子就砸了。
具体做法可以是要求系统驱动程序的开发商用形式化的方法开发,提供其正确/安全性的可验证的证明。
要是开发商说我的代码量太大,这要求太难了,微软正好可以回应:
“没错,何不把你的逻辑移到用户模式?”
~~~~
参考文献:
https://fortune.com/2024/08/03/crowdstrike-outage-fortune-500-companies-5-4-billion-damages-uninsured-losses/
https://www.crowdstrike.com/blog/falcon-content-update-preliminary-post-incident-report/
https://www.crowdstrike.com/wp-content/uploads/2024/08/Channel-File-291-Incident-Root-Cause-Analysis-08.06.2024.pdf
https://www.microsoft.com/en-us/security/blog/2024/07/27/windows-security-best-practices-for-integrating-and-managing-security-tools/
~~~~~~~~~~
猜你会喜欢:
谷歌对微软:代码管理工具哪家强?- 要集中还是要分布
后 C++ 演义(第一回、第二回) - 起底 C++ 发明人比雅尼
后 C++ 演义(第三回) - C++ 的最新发展
程序员护发秘籍 - 掌握这些工作技巧,包你不脱发
程序员的核心技能 - 以脱口秀的方式讲解程序员最重要的技能
如何做出保鲜十年的软件 - 老码农冒死披露行业内幕系列
dongbei 语言满月记事 - 一种基于东北方言的娱乐式程序设计语言
~~~~~~~~~~
关注老万故事会公众号:
本公众号不开赞赏不放广告。如果喜欢这篇文章,欢迎点赞、在看、转发。谢谢大家🙏
微信扫码关注该文公众号作者