本文是公开报告的摘要与解读,事实以原始报告为准。
2019 年 7 月 2 日,Cloudflare 发生全球性故障。13:31 UTC,一名工程师合并了包含 XSS 检测规则变更的已批准 pull request;13:37,TeamCity 完成规则构建和测试,没有发现 CPU 问题;13:42,自动化全球部署开始,故障随之出现。
问题规则包含一段会“灾难性回溯”的正则表达式,导致处理 HTTP/HTTPS 流量的 CPU 核心几乎达到 100% 使用率。边缘节点无法及时处理请求,访问任意 Cloudflare 域名的用户看到 502 错误页,Cloudflare Dashboard 和 API 也因为流量经过边缘而不可用。
报告给出的核心故障时长是 27 分钟。14:07,工程师执行“global terminate”全局终止 WAF;14:09,全球流量和 CPU 回到预期水平;14:52,WAF 在全球重新启用。
| 时间(UTC) | 事件 |
|---|---|
| 13:31 | 工程师合并包含 XSS WAF 规则变更的已批准 pull request |
| 13:37 | TeamCity 构建规则并运行测试,测试通过,未发现 CPU 问题 |
| 13:42 | 自动化全球部署开始,生成变更请求工单;27 分钟故障开始 |
| 13:45 | 第一个 PagerDuty 告警:外部合成 WAF 测试失败;随后其他端到端测试、全球流量下降告警、大量 502 和 PoP CPU 耗尽报告出现 |
| 13:45–14:00 | SRE 宣布 P0 并升级;伦敦工程团队中断内部演讲;最初怀疑是新型攻击;有负责人报告失去 80% 流量 |
| 14:00 | 确认 WAF 是原因,排除攻击;实时 CPU 数据、strace 和错误日志指向 WAF |
| 14:02 | 工程师提出使用“global terminate”;Cloudflare Access 故障导致控制台认证受阻,Jira 和构建系统也不可达,绕过流程不熟,部分凭据过期 |
| 14:07 | 执行全局 WAF 终止 |
| 14:09 | 全球流量和 CPU 回到预期水平,核心故障结束;其他防护机制仍在运行 |
| 14:09–14:52 | WAF 保持关闭;在一个城市用部分流量做正反测试,并先移除该城市的付费客户流量 |
| 14:52 | 团队确认已理解原因且修复到位,WAF 全球恢复 |
故障是全球性的。报告称问题规则运行在“全球 Cloudflare 网络中每一个处理 HTTP/HTTPS 流量的 CPU 核心”上,CPU 使用率接近 100%。用户访问任意 Cloudflare 域名都会看到 502 错误页,Cloudflare Dashboard 和 API 也不可用,因为它们同样经过 Cloudflare 边缘。
报告引述一位 Solutions Engineering 负责人对作者说,当时“失去了 80% 的流量”。这不是报告给出的精确统计,而是事故现场的业务判断;报告本身没有给出更细的请求比例或客户数量。
14:07 全局终止 WAF 后,核心流量在 14:09 恢复;WAF 直到 14:52 才重新启用。报告明确提到,WAF 关闭期间其他防护机制仍在运行。
触发点是 WAF Managed Rules 中的一条 XSS 检测规则。它走完了正常的 pull request、审批、构建和测试流程,但 WAF 规则变更绕过了 Cloudflare 常规的 DOG/PIG/Canary 分阶段发布。规则原本要以“simulate”模式部署,但报告指出:即使是 simulate 模式,规则也必须对真实流量执行,只是不拦截。
规则中的关键片段是 .*(?:.*=.*),报告为便于分析将其简化为 .*.*=.*。两个连续的贪婪 .* 可以用很多种方式划分同一段输入;当后面的字面量匹配失败时,PCRE 会回溯尝试这些可能性。报告给出的测量包括:x=x 需要 23 步;x= 加 20 个 x 需要 555 步;20 个 x 且没有 = 需要 4,067 步;再加一个分号后升到 5,353 步。
Lua WAF 使用的 PCRE 没有防止表达式失控的机制;更糟的是,一个本可限制 CPU 使用的保护在数周前的 WAF 重构中被误删。规则通过 Quicksilver 在几秒内分发到全球,报告给出的 p99 是 2.29 秒,因此故障几乎瞬间覆盖全网。
报告把这次事故描述为“多个原因的不太可能的汇聚”,而不是单一 bug。测试只验证规则是否匹配,不测量 CPU 消耗,测试套件运行时间也没有明显增加;SOP 允许非紧急规则不做分阶段发布就全球上线;回滚需要两次完整的 WAF 构建,速度太慢;全球流量下降告警触发偏晚;状态页更新偏慢;Cloudflare Access 故障又让控制台认证和绕过流程变得困难。
13:45 的第一个合成测试失败后,告警迅速扩展为全球 502 和 CPU 耗尽报告。SRE 在 14:00 前宣布 P0,伦敦工程团队中断内部演讲加入处置。最初怀疑是新型攻击,14:00 通过实时 CPU、strace 和错误日志确认问题来自 WAF,排除了攻击。
14:02 提出“global terminate”,但 Cloudflare Access 故障导致控制台认证不可用,Jira 和构建系统也不可达,绕过流程不熟悉,部分凭据已过期。14:07 终止操作执行,14:09 流量和 CPU 恢复。之后 WAF 保持关闭,团队在一个城市用部分流量做负向和正向测试,并先移除该城市的付费客户流量;14:52 确认理解原因且修复到位后,WAF 全球恢复。
报告列出的改进包括:重新引入被误删的 CPU 使用保护(已完成);人工检查 WAF Managed Rules 中全部 3,868 条规则(已完成);为所有规则增加性能分析(预计 7 月 19 日);切换到 RE2 或 Rust 正则引擎(预计 7 月 31 日);修改 SOP,实施分阶段发布,同时保留紧急全球部署能力;提供把 Dashboard 和 API 移出 Cloudflare 边缘的应急方式;自动化状态页更新;长期把 WAF 迁移到新的防火墙引擎。
第一,正则表达式是生产风险,不只是代码细节。一个短表达式就能让全球边缘 CPU 饱和;用户可控输入的正则必须使用线性时间引擎、设置超时或限制回溯。
第二,测试要测量性能,而不只是匹配结果。规则匹配正确但 CPU 爆炸,传统功能测试发现不了;性能基线、最坏输入和超时测试应进入发布门禁。
第三,配置变更要像代码一样灰度。WAF 规则绕过 DOG/PIG/Canary,直接全球部署,是事故能瞬间扩散的关键;非紧急变更应先小流量验证。
第四,kill switch 必须快、可用、练过。事故中控制台认证依赖 Cloudflare Access,而 Access 也故障;绕过流程不熟、凭据过期,都会拉长恢复时间。
第五,状态页和告警要独立且自动化。全球流量下降告警触发偏晚、状态页更新偏慢,会放大外部感知的故障时长。
第六,删除保护要留下记录。CPU 保护在重构中被误删,说明安全机制也需要测试和审计,不能默默消失。
合上页面,先回答,再展开对照。
.*(?:.*=.*) 为什么会发生灾难性回溯?报告给了哪些测量?.* 可以用多种方式划分同一段输入,后续字面量匹配失败时 PCRE 会回溯尝试;报告测量了 x=x 23 步、20 个 x 无等号 4,067 步、带分号 5,353 步等情况。
小程序版
微信扫码,随时查命令