Memory safety for web fonts

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

Published: March 19, 2025

Skrifa is written in Rust, and created as a replacement for FreeType to make font processing in Chrome secure for all our users. Skifra takes advantage of Rust's memory safety, and lets us iterate faster on font technology improvements in Chrome. Moving from FreeType to Skrifa allows us to be both agile and fearless when making changes to our font code. We now spend far less time fixing security bugs, resulting in faster updates, and better code quality.

This post shares why Chrome has moved away from FreeType, and some interesting technical details of the improvements this move has enabled.

Why replace FreeType?

The web is unique in that it allows users to fetch untrusted resources from a wide variety of untrusted sources with the expectation that things will just work, and that they are safe in doing so. This assumption is generally correct, but keeping that promise to users comes at a cost. For example, to use a web font safely (a font delivered over the network) Chrome employs several security mitigations:

  • Font processing is sandboxed per the rule of two: they are untrustworthy and the consuming code is unsafe.
  • Fonts are passed through the OpenType Sanitizer prior to processing.
  • All the libraries involved in decompressing and processing fonts are fuzz tested.

Chrome ships with FreeType and makes use of it as the primary font processing library on Android, ChromeOS, and Linux. That means a lot of users are exposed if there is a vulnerability in FreeType.

The FreeType library is used by Chrome to compute metrics and load hinted outlines from fonts. Overall, use of FreeType has been a huge win for Google. It does a complex job, and does it well, we rely on it extensively and contribute back to it. However, it is written in unsafe code and has its origins in a time when malicious inputs were less likely. Merely keeping up with the stream of issues found by fuzzing costs Google at least 0.25 full time software engineers. Worse, we observably don't find everything or find things only after the code has shipped to users.

This pattern of problems is not unique to FreeType, we observe that other unsafe libraries admit issues even when we use the best software engineers we can find, code review every change, and require tests.

Why issues keep sneaking in

When we evaluated FreeType's security, we observed three main classes of issue to occur (non-exhaustive):

Use of an unsafe language

Pattern/Issue Example
Manual memory management
Unchecked array access CVE-2022-27404
Integer overflows During execution of embedded virtual machines for TrueType hinting of CFF drawing and hinting
https://issues.oss-fuzz.com/issues?q=FreeType%20Integer-overflow
Incorrect use of zeroing versus non-zeroing allocation Discussion in https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/94, 8 fuzzer issues found afterwards
Invalid casts See the following row on macro usage

Project specific issues

Pattern/Issue Example
Macros obscure lack of explicit size typing
  • Macros such as FT_READ_* and FT_PEEK_* obscure what integer types are being used, hiding that C99 types with explicit sizes (int16_t, etc) are not used
New code consistently adds bugs, even when written defensively.
  • COLRv1 and OT-SVG support both produced issues
  • Fuzzing finds some, but not necessarily all, #32421, #52404
Lack of tests
  • Crafting test fonts is time consuming and difficult

Dependency issues

Fuzzing has repeatedly identified issues in libraries FreeType depends on, such as bzip2, libpng, and zlib. As an example, compare freetype_bdf_fuzzer: Use-of-uninitialized-value in inflate.

Fuzzing isn't enough

Fuzzing–automated testing with a wide range of inputs, including randomized invalid ones–is meant to find many of the types of issues that get into the stable release of Chrome. We fuzz FreeType as part of Google's oss-fuzz project. It does find issues, but fonts have proven somewhat resistant to fuzzing, for the following reasons.

Font files are complex, comparable to video files as they contain multiple different types of information. Font files are a container format for multiple tables, where each table serves a different purpose in processing text and fonts together to produce a correctly positioned glyph on the screen. In a font file you will find:

  • Static metadata such as font names and parameters for variable fonts.
  • Mappings from Unicode characters to glyphs.
  • A complex ruleset and grammar for screen layout of glyphs.
  • Visual information: Glyph shapes and image information describing what the glyphs placed on the screen look like.
    • The visual tables can in turn include TrueType hinting programs, which are mini programs executed to change the glyph shape.
    • Char strings in the CFF or CFF2 tables which are imperative curve drawing and hinting instructions executed in the CFF rendering engine.

There is complexity in font files equivalent to having its own programming language and state machine processing, requiring specific virtual machines to execute them.

Because of the complexity of the format, fuzzing has shortcomings in finding issues in font files.

Good code coverage or fuzzer progress is difficult to achieve for the following reasons:

  • Fuzzing TrueType hinting programs, CFF char strings and OpenType layout using simple bit-flipping/shift/insertion/deletion-style mutators struggles to reach all combinations of states.
  • Fuzzing needs to at least produce partially valid structures. Random mutation rarely does so, making good coverage hard to achieve, particularly for deeper levels of code.
  • The current fuzzing efforts in ClusterFuzz and oss-fuzz are not yet using structure-aware mutation. Use of grammar- or structure-aware mutators might help avoid production of variants that are rejected early, at the cost of taking more time to develop, and introducing chances which miss parts of the search space.

Data in multiple tables needs to be in sync for fuzzing to make progress:

  • The usual mutation patterns of fuzzers don't produce partially valid data so many iterations get rejected and progress becomes slow.
  • The glyph mapping, the OpenType layout tables and glyph drawing are connected and depend on each other, forming a combinatorial space whose corners are hard to reach with fuzzing.
  • For example, the high-severity tt_face_get_paint COLRv1 vulnerability took more than 10 months to find.

Despite our best efforts, font security issues have repeatedly reached end users. Replacing FreeType with a Rust alternative will prevent multiple entire classes of vulnerability.

Skrifa in Chrome

Skia is the graphics library used by Chrome. Skia relies on FreeType to load metadata and letterforms from fonts. Skrifa is a Rust library, part of the Fontations family of libraries, that provides a safe replacement for the parts of FreeType used by Skia.

To transition FreeType to Skia the Chrome team developed a new Skia font backend based on Skrifa and gradually rolled out the change to users:

For the integration into Chrome, we rely on the smooth integration of Rust into the codebase introduced by the Chrome security team.

In the future we'll switch to Fontations for operating system fonts as well, starting with Linux and ChromeOS, then on Android.

Safety, first and foremost

Our primary goal is to reduce (or ideally, eliminate!) security vulnerabilities that are caused by out of bounds access to memory. Rust provides this out of the box as long as you avoid any unsafe code blocks.

Our performance goals require us to perform one operation that is currently unsafe: reinterpretation of arbitrary bytes as a strongly typed data structure. This allows us to read the data from a font file without performing unnecessary copies and is essential for producing a fast font parser.

To avoid our own unsafe code, we've chosen to outsource this responsibility to bytemuck which is a Rust library designed specifically for this purpose and is widely tested and used across the ecosystem. Concentrating raw data reinterpretation in bytemuck ensures we have this functionality in one place and audited, and avoid repeating unsafe code for the purpose. The safe transmute project aims to incorporate this functionality directly into the Rust compiler and we will make the switch as soon as it is available.

Correctness matters

Skrifa is built out of independent components where most data structures are designed to be immutable. This improves readability, maintainability and multithreading. It also makes the code more amenable to unit testing. We've taken advantage of this opportunity and have produced a suite of roughly 700 unit tests that cover our full stack from low level parsing routines to high level hinting virtual machines.

Correctness also implies fidelity and FreeType is highly regarded for its generation of high quality outlines. We must match this quality to be a suitable replacement. To that end, we have built a bespoke tool called fauntlet that compares the output of Skrifa and FreeType for batches of font files across a wide range of configurations. This affords us some assurance that we can avoid regressions in quality.

In addition, before the integration into Chromium, we ran a wide set of pixel comparisons in Skia, comparing FreeType rendering to Skrifa and Skia rendering to ensure the pixel differences are absolutely minimal, in all required rendering modes (across different antialiasing and hinting modes).

Fuzz testing is an important tool for determining how a piece of software will react to malformed and malicious inputs. We've been continuously fuzzing our new code since June of 2024. This covers the Rust libraries itself and the integration code. While the fuzzer has found (as of this writing) 39 bugs, it's worth noting that none of these have been security critical. They may cause undesired visual results or even controlled crashes, but won't lead to exploitable vulnerabilities.

Onward!

We are very pleased with the results of our efforts to use Rust for text. Delivering safer code to users and gaining developer productivity is a huge win for us. We plan to continue to seek opportunities to use Rust in our text stacks. If you'd like to know more, Oxidize outlines some of Google Fonts future plans.