← 返回信息流
AI 资讯Hacker News·4 天前

发现 hyper HTTP 库存在安全漏洞

原标题:We found a bug in the hyper HTTP library

速览

安全研究人员在广泛使用的 Rust HTTP 库 hyper 中发现了一个安全漏洞。该漏洞可能影响依赖该库的应用程序,提醒开发者及时更新以修复潜在风险。

AI 深度解读

Cloudflare 深度解析:在 hyper HTTP 库中发现的一个隐蔽 Bug

背景

Cloudflare 的 Images 服务是一个基于 Rust 构建的核心组件,运行在 Cloudflare 边缘网络的每一台机器上。为了处理客户端连接,该服务广泛使用了 hyper——一个用于 Rust 语言的开源 HTTP 库。

去年,Cloudflare 引入了 Images Binding(绑定),旨在为 Workers 平台提供自定义的、可编程的远程图像处理工作流。到了 2025 年底,为了提供更直接、更本地的连接,团队对这一 Binding 进行了重新架构,使其能够直接在 Workers 运行时与 Images 服务之间建立连接,从而绕过原有的中间层。

然而,在重新架构上线后不久,团队收到了用户报告:通过 Binding 发起的图像转换请求出现间歇性失败。这些失败仅针对较大的图像文件,且现象极为诡异:返回的 HTTP 状态码为 200(成功),日志中没有任何错误记录,但图像数据却被截断。例如,一个预期为 2MB 的响应,实际接收到的可能只有几百 KB。

经过六周的排查,团队最终发现这是一个存在于 hyper 库中的竞态条件(race condition)Bug,仅在特定条件下触发,并最终仅用四行代码便修复了它。

核心内容

1. 架构演进:从 FL 到内部 Binding

要理解这个 Bug 的成因,首先需要了解 Cloudflare 架构的演变。

在 Cloudflare 平台上,开发者通过 Binding 将全栈应用组合在一起。Images Binding 将图像优化与交付解耦,允许开发者在不返回 HTTP 响应作为最终输出的情况下,对图像进行转码、合成或操作。它支持任意顺序应用优化参数,并允许 Worker 直接将图像数据传递给 Images API,链式操作后以流的形式获取结果。

原有架构(FL 中间件): 最初,所有传入 Cloudflare 网络的流量都经过一个名为 FL 的内部中间件服务。FL 负责安全检查和性能功能,并将请求路由到后端。在这种架构下,数据从 Workers 运行时流经 FL,再到达 Images 服务。这种设计虽然符合初始发布需求,但也带来了耦合问题:Binding 的每一次变更都必须遵循 FL 的发布周期。

新架构(内部 Binding): 2025 年 12 月,Images 团队用一个新的内部中间件服务取代了 FL。这个新服务是一个运行在同一台机器上的内部 Worker Binding。

  • 连接方式改变:原架构通过网络套接字(Network Sockets)传输数据,涉及 DNS 查找和路由等网络栈开销;新架构使用 Unix 套接字(Unix Sockets)直接连接同一台机器上的服务。
  • 优势:这消除了 FL 的处理开销,使请求路径更快,并赋予团队对 Binding 发布的独立控制权。

2. Bug 的触发场景与现象

新架构上线仅数天,首个客户报告便随之而来。

客户场景: 一位客户采用了非标准的两层图像处理设置:

  1. 内层管道:使用 Images Binding 将 R2 存储中的多个大源图像(如 JPEG 背景 + PNG 叠加层)合成一张组合 JPEG。
  2. 外层管道:通过 URL 接口对合成结果进行进一步的压缩、转码和缩放。

故障现象: Bug 起源于内层管道的返回路径。内层管道(Transformation Binding)在处理合成时,返回给外层管道(Transformation URL)的响应被静默截断。

  • 外层管道收到 HTTP 200 状态码。
  • Content-Length 头信息承诺了数 MB 的数据量。
  • 实际接收到的主体数据仅为预期的一小部分(例如,预期 3.3 MB,实际仅收到 ~200 KB)。
  • 外层管道报错:error reading a body from connection: end of file before message length reached(从连接读取主体时出错:在达到消息长度前遇到文件结尾)。

对于浏览器而言,收到截断的图像会导致图像部分渲染(如底部缺失或变灰)或完全无法解码。

3. 调试过程:在黑暗中摸索

团队通过“由外向内”的方式逐层测试,以隔离截断发生的位置。

  • 构建复现环境:团队构建了一个模拟客户嵌套设置的 Worker,并逐步剥离层级,最终仅通过 Binding 即可触发 Bug。脚本测试显示,25 次请求中有 19 次失败,且失败时接收到的数据量(约 200 KB)恰好接近生产环境中的套接字缓冲区大小。这证实了问题与客户配置无关,并提供了可靠的复现手段。
  • 排除超时假设:初期怀疑截断与超时有关,但分析显示截断与请求持续时间无相关性。
  • 版本测试:团队测试了 hyper 的不同版本(0.14.x, 1.7, 1.8),发现 Bug 在所有版本中均存在,排除了上游版本修复的可能性。
  • 本地复现:团队在 macOS 和 Debian VM 上运行本地集成测试。即使在负载较轻的情况下,Bug 依然难以稳定复现,这暗示了问题的隐蔽性。

4. 根本原因:hyper 库中的竞态条件

问题的核心在于 hyper 库如何处理从 Images 服务到客户端的数据流。

数据流动机制:

  1. Images 服务读取输入,执行优化操作,并将编码后的整个图像作为单个内存块传递给 hyper
  2. hyper 将响应数据写入其内部缓冲区。此时,hyper 认为编码工作已完成,因为它拥有了所有需要发送的字节。
  3. 下一步是将内部缓冲区刷新(flush)到套接字的出站缓冲区,从而将数据从 Images 服务传输到另一端的中间件/客户端。

竞态条件的产生:

  • 快速读取者:如果另一端读取数据的速度足够快,hyper 可以一次性刷新所有数据,因为出站缓冲区始终有空间。发送完成后,hyper 在套接字上发出 shutdown 信号,表示连接结束。
  • 慢速读取者:如果另一端读取稍慢(即使仅慢几毫秒),出站缓冲区就会填满。此时,hyper 需要等待缓冲区腾出空间才能继续写入。

Bug 的本质: 在特定的竞态条件下,当 hyper 等待缓冲区空间时,如果连接状态管理出现细微偏差,可能导致连接在数据完全发送前被错误地关闭或重置。由于 Images 服务将图像作为单个内存块传递,且 hyper 在写入前认为任务已完成,这种微小的时序差异导致了数据截断。尽管 HTTP 状态码仍为 200,但实际传输的数据量不足,且没有明确的错误日志,使得问题极难追踪。

关键要点

  • 隐蔽的竞态条件:Bug 存在于 Rust 的 hyper HTTP 库中,仅在特定网络延迟和缓冲区状态下触发,表现为间歇性数据截断。
  • 架构变更是诱因:从 FL 中间件迁移到基于 Unix 套接字的内部 Binding,改变了数据流的时序特性,暴露了 hyper 在处理快速/慢速读取者时的边界情况。
  • 误导性信号:故障表现为 HTTP 200 成功状态码,且无错误日志,仅通过数据长度不匹配(Content-Length 与实际接收量不符)发现异常。
  • 调试策略:通过构建最小化复现环境、排除超时因素、测试多版本库以及分析缓冲区大小与失败数据量的关联,团队成功定位了问题。
  • 低成本修复:尽管排查过程复杂,但最终修复仅需修改 hyper 库中的四行代码,证明了深入理解底层库行为的重要性。

意义与影响

  1. 对 Rust 生态的警示hyper 作为 Rust 领域最流行的 HTTP 库之一,其底层竞态条件表明,即使在成熟的开源库中,处理高并发、低延迟场景下的缓冲区管理仍可能存在细微缺陷。开发者在使用异步 I/O 库时,需警惕非确定性的时序问题。
查看原文 →blog.cloudflare.com