ウェブフォントのメモリ安全性

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

公開日: 2025 年 3 月 19 日

Skrifa は Rust で記述され、Chrome でのフォント処理をすべてのユーザーにとって安全にするために FreeType の代替として作成されました。Skrifa は Rust のメモリ安全性という利点を活かし、Chrome のフォント技術の改善をより迅速に繰り返すことを可能にします。FreeType から Skrifa に移行することで、フォントコードの変更をアジャイルかつ大胆に行えるようになりました。セキュリティ バグの修正にかかる時間が大幅に短縮され、アップデートの迅速化とコード品質の向上につながっています。

この投稿では、Chrome が FreeType から移行した理由と、この移行によって実現した改善点に関する興味深い技術的な詳細について説明します。

FreeType を置き換える理由

ウェブは、ユーザーがさまざまな信頼できないソースから信頼できないリソースを取得し、それが機能し、安全であると期待できるという点で独特です。この仮定は一般的に正しいのですが、ユーザーにその約束を守るにはコストがかかります。たとえば、ウェブフォント(ネットワーク経由で配信されるフォント)を安全に使用するために、Chrome ではいくつかのセキュリティ対策が採用されています。

Chrome には FreeType が同梱されており、Android、ChromeOS、Linux での主要なフォント処理ライブラリとして使用されています。つまり、FreeType に脆弱性があると、多くのユーザーが影響を受けることになります。

FreeType ライブラリは、Chrome が指標を計算し、フォントからヒント付きのアウトラインを読み込むために使用されます。全体として、FreeType の使用は Google にとって大きなメリットとなっています。複雑なジョブを適切に実行し、広範に利用され、貢献も行われています。ただし、安全でないコードで記述されており、悪意のある入力が少なかった時代に作成されたものです。ファジングによって検出された問題のストリームに対応するだけで、Google は少なくとも 0.25 人のフルタイムのソフトウェア エンジニアを必要とします。さらに、すべてが見つからない、またはコードがユーザーにリリースされた後にのみ見つかることもあります。

この問題のパターンは FreeType に固有のものではありません。他の安全でないライブラリでも、最高のソフトウェア エンジニアを起用し、すべての変更をコードレビューし、テストを必須にしても、問題が発生することが確認されています。

問題が潜り込んでくる理由

FreeType のセキュリティを評価したところ、主に次の 3 つのクラスの問題が発生することがわかりました(網羅的ではありません)。

安全でない言語の使用

柄/問題
手動メモリ管理
未チェックの配列アクセス 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_* などのマクロは、使用されている整数型を不明瞭にし、明示的なサイズ(int16_t など)を持つ C99 型が使用されていないことを隠蔽します。
新しいコードは、防御的に記述してもバグを常に発生させる。
  • COLRv1 と OT-SVG のサポートで両方とも問題が発生した
  • ファジングでは一部の #32421#52404 が検出されますが、すべてが検出されるとは限りません。
テストの不足
  • テストフォントの作成には時間がかかり、難しい

依存関係に関する問題

ファジングにより、FreeType が依存するライブラリ(bzip2、libpng、zlib など)で問題が繰り返し特定されています。たとえば、freetype_bdf_fuzzer: inflate での未初期化値の使用を比較します。

ファジングだけでは不十分

ファジング(ランダムな無効な入力を含む幅広い入力による自動テスト)は、Chrome の安定版リリースで発生する多くの種類の問題を見つけることを目的としています。Google の oss-fuzz プロジェクトの一環として、FreeType のファジングを行っています。問題は検出されますが、フォントは次の理由でファジングにやや耐性があることがわかっています。

フォントファイルは複雑で、さまざまな種類の情報が含まれているため、動画ファイルに匹敵します。フォント ファイルは複数のテーブルのコンテナ形式です。各テーブルは、テキストとフォントを一緒に処理して画面上に正しく配置されたグリフを生成する際に、それぞれ異なる目的で使用されます。フォント ファイルには次のものが含まれています。

  • フォント名や可変フォントのパラメータなどの静的メタデータ。
  • Unicode 文字からグリフへのマッピング。
  • グリフの画面レイアウトの複雑なルールセットと文法。
  • 視覚情報: 画面に配置されたグリフがどのように見えるかを説明するグリフの形状と画像情報。
    • ビジュアル テーブルには、グリフの形状を変更するために実行されるミニプログラムである TrueType ヒンティング プログラムを含めることができます。
    • CFF または CFF2 テーブル内の char 文字列。CFF レンダリング エンジンで実行される必須の曲線描画とヒンティングの指示。

フォントファイルには、独自のプログラミング言語とステート マシン処理に相当する複雑さがあり、実行には特定の仮想マシンが必要です。

形式が複雑なため、ファジングではフォント ファイルの問題を見つけることができません。

コード カバレッジやファザーの進捗状況を良好に保つのが難しい理由は次のとおりです。

  • 単純なビット反転/シフト/挿入/削除スタイルのミューテーターを使用して TrueType ヒンティング プログラム、CFF 文字列、OpenType レイアウトをファジングすると、すべての状態の組み合わせに到達するのが困難になります。
  • ファジングでは、少なくとも部分的に有効な構造を生成する必要があります。ランダム ミューテーションでは、このようなことはほとんど起こりません。そのため、特にコードの深いレベルでは、十分なカバレッジを達成することが困難になります。
  • ClusterFuzz と oss-fuzz の現在のファジングでは、構造認識ミューテーションはまだ使用されていません。文法や構造を認識するミューテーターを使用すると、開発に時間がかかり、検索空間の一部を見逃す可能性が生じるという代償を伴いますが、早期に拒否されるバリアントの生成を回避できる可能性があります。

ファジングを進行させるには、複数のテーブルのデータを同期する必要があります。

  • ファザーの通常のミューテーション パターンでは部分的に有効なデータが生成されないため、多くのイテレーションが拒否され、進行が遅くなります。
  • グリフ マッピング、OpenType レイアウト テーブル、グリフ描画は相互に接続され、依存し合っているため、ファジングでは到達しにくい隅を持つ組み合わせ空間を形成しています。
  • たとえば、重大度の高い tt_face_get_paint COLRv1 の脆弱性は、発見に 10 か月以上かかりました。

Google は最善の努力を払ってきましたが、フォントのセキュリティの問題がエンドユーザーに繰り返し影響を及ぼしています。FreeType を Rust の代替に置き換えることで、複数のクラスの脆弱性を防ぐことができます。

Chrome の Skrifa

Skia は、Chrome で使用されるグラフィック ライブラリです。Skia は FreeType を使用して、フォントからメタデータと文字図形を読み込みます。Skrifa は、Fontations ライブラリ ファミリーの一部である Rust ライブラリです。Skia で使用される FreeType の部分を安全に置き換える機能を提供します。

FreeType から Skia への移行のため、Chrome チームは Skrifa に基づく新しい Skia フォント バックエンドを開発し、ユーザーに段階的に変更をロールアウトしました。

Chrome への統合については、Chrome セキュリティ チームが導入した、Rust をコードベースにスムーズに統合する機能を利用しています。

今後、オペレーティング システムのフォントも Fontations に切り替える予定です。まず Linux と ChromeOS から始め、次に Android に切り替えます。

安全性を最優先

主な目標は、メモリへの境界外アクセスによって発生するセキュリティの脆弱性を軽減(理想的には排除)することです。Rust では、安全でないコードブロックを回避する限り、この機能がすぐに利用できます。

パフォーマンス目標を達成するには、現在安全でない操作(任意のバイトを厳密に型指定されたデータ構造として再解釈する)を実行する必要があります。これにより、不要なコピーを実行せずにフォント ファイルからデータを読み取ることができ、高速なフォント パーサーを生成するために不可欠です。

安全でないコードを避けるため、この責任を bytemuck に委託することにしました。これは、この目的のために特別に設計された Rust ライブラリであり、エコシステム全体で広くテストされ、使用されています。生のデータの再解釈を bytemuck に集中させることで、この機能が 1 か所に集約され、監査されるようになり、目的のために安全でないコードを繰り返すことを回避できます。安全な transmute プロジェクトは、この機能を Rust コンパイラに直接組み込むことを目指しており、利用可能になり次第、切り替えを行います。

正確性が重要

Skrifa は独立したコンポーネントで構成されており、ほとんどのデータ構造は不変になるように設計されています。これにより、読みやすさ、保守性、マルチスレッドが向上します。また、コードを単体テストに適したものにすることもできます。この機会を利用して、低レベルの解析ルーチンから高レベルのヒンティング仮想マシンまで、フルスタックをカバーする約 700 個の単体テストのスイートを作成しました。

正確性は忠実度も意味します。FreeType は高画質のアウトラインの生成で高く評価されています。この品質に一致するものでなければ、適切な代替品とは言えません。そのため、Google は fauntlet というカスタムツールを構築しました。このツールは、幅広い構成でフォント ファイルのバッチに対して Skrifa と FreeType の出力を比較します。これにより、品質の回帰を回避できるという確信が得られます。

また、Chromium に統合する前に、Skia で幅広いピクセル比較を実施し、FreeType レンダリングと Skrifa レンダリング、Skia レンダリングを比較して、必要なすべてのレンダリング モード(さまざまなアンチエイリアシング モードとヒンティング モード)でピクセルの差が最小限になるようにしました。

ファジング テストは、ソフトウェアが不正な入力や悪意のある入力にどのように反応するかを判断するための重要なツールです。2024 年 6 月以降、新しいコードのファジングを継続的に実施しています。これには、Rust ライブラリ自体と統合コードが含まれます。この執筆時点で、ファザーは 39 個のバグを発見していますが、そのいずれもセキュリティ上重大なものではないことに注意してください。望ましくない視覚的な結果や、制御されたクラッシュを引き起こす可能性がありますが、悪用可能な脆弱性につながることはありません。

次へ!

テキストに Rust を使用する取り組みの結果には非常に満足しています。ユーザーに安全なコードを提供し、デベロッパーの生産性を向上させることは、Google にとって大きなメリットです。今後も、テキスト スタックで Rust を使用する機会を探していく予定です。詳しくは、Oxidize で Google Fonts の今後の計画をご覧ください。