Memory Safety for Web Fonts
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 |
|
New code consistently adds bugs, even when written defensively. |
|
Lack of tests |
|
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.
- The visual tables can in turn include TrueType hinting programs, which
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:
- In Chrome 128 (August 2024) we enabled
Fontations
for use in less commonly used font formats, such as for color fonts and
CFF2, as a safe trial run. - In Chrome 133 (February 2025) we enabled
Fontations for all web
fonts usage on Linux, Android and ChromeOS, and for web fonts usage as
fallback on Windows and Mac—in cases where the system does not support a
font format but Chrome needs to display it.
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.