网络字体的内存安全

Dominik Röttsches
Dominik Röttsches
Rod Sheeter
Rod Sheeter
Chad Brokaw
Chad Brokaw

发布时间:2025 年 3 月 19 日

Skrifa 是用 Rust 编写的,旨在替代 FreeType,以确保 Chrome 中的字体处理对所有用户都是 安全的。 Skrifa 利用了 Rust 的内存安全性,让我们能够更快地迭代 Chrome 中的字体技术改进。 从 FreeType 迁移到 Skrifa 后,我们在更改字体代码时既能保持敏捷,又能无所畏惧。现在,我们修复安全错误所花费的时间大大减少,从而加快了更新速度,并提高了代码质量。

这篇博文将分享 Chrome 弃用 FreeType 的原因,以及此举所带来的改进的一些有趣的技术细节。

为什么要替换 FreeType?

Web 的独特之处在于,它允许用户从各种不受信任的来源获取不受信任的资源,并期望一切都能正常运行,并且这样做是安全的。这种假设通常是正确的,但要向用户兑现这一承诺,需要付出一定的代价。例如,为了安全地使用 Web 字体(通过网络提供的字体),Chrome 采用了多种安全缓解措施:

Chrome 附带 FreeType,并将其用作 Android、ChromeOS 和 Linux 上的主要字体处理库。这意味着,如果 FreeType 中存在漏洞,大量 用户会受到影响。

Chrome 使用 FreeType 库来计算指标并从字体加载提示轮廓。总的来说,使用 FreeType 对 Google 来说是一项巨大的胜利。它完成了一项复杂的工作,并且做得很好,我们广泛依赖它并为其做出贡献。但是,它是用不安全的代码编写的,并且起源于恶意输入不太可能出现的时代。仅仅跟上模糊测试发现的问题流,Google 至少需要 0.25 名全职软件工程师。更糟糕的是,我们显然无法发现所有问题,或者只能在代码发布给用户后才发现问题。

这种问题模式并非 FreeType 独有,我们观察到,即使我们使用我们能找到的最优秀的软件工程师,对每一项更改进行代码审核,并要求进行测试,其他不安全的库也会出现问题。

为什么问题会不断出现

在评估 FreeType 的安全性时,我们观察到出现了三种主要类型的问题(非详尽):

使用不安全的语言

模式/问题 示例
手动内存管理
未经检查的数组访问 CVE-2022-27404
整数溢出 在执行嵌入式虚拟机以进行 CFF 绘图和提示的 TrueType 提示期间
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
错误地使用归零与非归零分配 https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94 中的讨论,之后发现了 8 个模糊测试器问题
无效的转换 请参阅下一行关于宏用法的说明

项目特定问题

模式/问题 示例
宏掩盖了缺少显式大小类型的问题
  • FT_READ_*FT_PEEK_* 等宏掩盖了正在使用的整数类型,隐藏了未使用具有显式大小的 C99 类型(int16_t 等)的问题
即使采用防御性编写方式,新代码也会始终添加 bug。
  • COLRv1 和 OT-SVG 支持都产生了问题
  • 模糊测试发现了一些问题,但不一定全部发现,#32421#52404
缺少测试
  • 制作测试字体既耗时又困难

依赖项问题

模糊测试反复发现了 FreeType 所依赖的库(例如 bzip2、libpng 和 zlib)中的问题。例如,比较 freetype_bdf_fuzzer: 在 inflate 中使用未初始化的值

模糊测试还不够

模糊测试(使用各种输入(包括随机无效输入)进行的自动测试)旨在发现 Chrome 稳定版中出现的许多类型的问题。我们对 FreeType 进行模糊测试,这是 Google 的 oss-fuzz 项目的一部分。它确实发现了问题,但事实证明,字体在某种程度上能够抵抗模糊测试,原因如下。

字体文件很复杂,与视频文件类似,因为它们包含多种不同类型的信息。字体文件是多个表的容器格式,其中每个表在处理文本和字体以在屏幕上生成正确位置的字形时都有不同的用途。在字体文件中,您会发现:

  • 静态元数据,例如字体名称和可变字体的参数。
  • 从 Unicode 字符到字形的映射。
  • 用于字形屏幕布局的复杂规则集和语法。
  • 视觉信息:字形形状和图片信息,用于描述放置在屏幕上的字形的外观。
    • 视觉表反过来可以包含 TrueType 字体微调程序,这些程序是用于更改字形形状的小程序。
    • CFF 或 CFF2 表中的字符字符串,这些字符串是在 CFF 渲染引擎中执行的命令式曲线绘制和字体微调指令。

字体文件的复杂性相当于拥有自己的编程语言和状态机处理,需要特定的虚拟机来执行它们。

由于格式的复杂性,模糊测试在查找字体文件中的问题方面存在不足。

由于以下原因,很难实现良好的代码覆盖率或模糊测试器进度:

  • 使用简单的位翻转/移位/插入/删除样式突变器对 TrueType 提示程序、CFF 字符字符串和 OpenType 布局进行模糊测试,很难达到所有状态组合。
  • 模糊测试至少需要生成部分有效结构。随机突变很少这样做,因此很难实现良好的覆盖率,尤其是对于更深层次的代码。
  • ClusterFuzz 和 oss-fuzz 中当前的模糊测试工作尚未采用结构感知突变。使用语法感知或结构感知突变器可能有助于避免生成早期被拒绝的变体,但代价是需要花费更多时间进行开发,并引入可能遗漏部分搜索空间的机会。

多个表中的数据需要同步,模糊测试才能取得进展:

  • 模糊测试器的常用突变模式不会生成部分有效数据,因此许多迭代会被拒绝,进度会变慢。
  • 字形映射、OpenType 布局表和字形绘制相互关联并相互依赖,形成一个组合空间,模糊测试很难触及该空间的各个角落。
  • 例如,严重性较高的 tt_face_get_paint COLRv1 漏洞花费了 10 个多月才被发现。

尽管我们尽了最大的努力,但字体安全问题还是反复影响了最终用户。用 Rust 替代方案替换 FreeType 将防止多种整个类别的漏洞。

Chrome 中的 Skrifa

Skia 是 Chrome 使用的图形库。Skia 依赖于 FreeType 从字体加载元数据和字形。 Skrifa 是一个 Rust 库,是 Fontations 系列库的一部分,它为 Skia 使用的 FreeType 部分提供了安全替代方案。

为了将 FreeType 转换为 Skia,Chrome 团队开发了一个基于 Skrifa 的新 Skia 字体后端,并逐步向用户推出了这项更改:

为了集成到 Chrome 中,我们依赖于 Rust 与 由 Chrome 安全 团队引入的代码库的顺利集成。

未来,我们还将为操作系统字体切换到 Fontations,首先是 Linux 和 ChromeOS,然后是 Android。

安全第一

我们的主要目标是减少(或理想情况下,消除!)因越界访问内存而导致的安全漏洞。只要您避免任何不安全的代码块,Rust 就会提供此功能。

我们的性能目标要求我们执行一项当前不安全的操作:将任意字节重新解释为强类型数据结构。 这使我们能够从字体文件中读取数据,而无需执行不必要的 复制,并且对于生成 快速字体解析器至关重要。

为了避免我们自己的不安全代码,我们选择将此责任外包给 bytemuck,这是一个专门为此目的而设计的 Rust 库,并且在整个生态系统中经过了广泛的测试和使用。将原始数据重新解释集中在 bytemuck 中,可确保我们在一个位置拥有此功能并经过审核,并避免为此目的重复不安全的代码。安全转换 项目旨在 将此功能直接集成到 Rust 编译器中,我们将在 该功能可用后立即进行切换。

正确性至关重要

Skrifa 由独立组件构建而成,其中大多数数据结构都设计为不可变。这提高了可读性、可维护性和多线程处理能力。它还使代码更适合单元测试。我们利用了这一机会,制作了一套大约 700 个单元测试,涵盖了从低级解析例程到高级提示虚拟机的完整堆栈。

正确性还意味着保真度,FreeType 因生成高质量轮廓而备受赞誉。我们必须达到此质量才能成为合适的替代方案。为此,我们构建了一个名为 fauntlet 的定制工具,用于比较 Skrifa 和 FreeType 在各种配置下对批量字体文件的输出。这让我们有了一定的保证,可以避免质量下降。

此外,在集成到 Chromium 之前,我们在 Skia 中进行了一系列广泛的像素比较,将 FreeType 渲染与 Skrifa 渲染进行比较,并将 Skrifa 渲染与 Skia 渲染进行比较,以确保在所有必需的渲染模式(跨不同的抗锯齿和提示模式)下,像素差异绝对最小。

模糊测试是 确定软件如何对格式有误和恶意 输入做出反应的重要工具。自 2024 年 6 月以来,我们一直在不断对新代码进行模糊测试。这涵盖了 Rust 库本身和集成代码。虽然模糊测试器(截至撰写本文时)发现了 39 个 bug,但值得注意的是,这些 bug 都不是安全关键型 bug 。它们可能会导致不希望出现的视觉效果,甚至会导致受控崩溃,但不会导致可利用的漏洞。

继续前进!

我们对使用 Rust 处理文本所取得的成果感到非常满意。向用户提供更安全的代码并提高开发者工作效率对我们来说是一项巨大的胜利。 我们计划继续寻找在文本堆栈中使用 Rust 的机会。如果您想了解更多信息, Oxidize 概述了 Google Fonts 的一些未来计划。