3月20日、Chrome for Developersが「Memory safety for web fonts」と題した記事を公開した。この記事では、ChromeがWebフォントのメモリ安全性を向上させるためにRust製ライブラリ「Skrifa」を導入した背景や技術的な詳細について詳しく紹介されている。
以下に、その内容を紹介する。
概要
Chromeのフォント処理は、従来FreeTypeというライブラリを使用していた。FreeType自体は複雑なフォント処理を支えてきた実績あるライブラリだが、CやC++などのメモリ管理が手動で行われる言語で書かれているため、セキュリティ上のリスクが少なくなかった。そこで、ChromeではRustのメモリ安全性を活用する新たなフォント処理ライブラリ「Skrifa」を導入し、セキュリティ強化と開発効率向上の両立を図っている。
Rust言語には原則としてメモリ安全性が保証されるという大きな利点がある。そのため、多数のユーザーが利用するChromeにおいて、悪意あるフォントデータによって引き起こされる潜在的な脆弱性を低減するうえで有効だとしている。Skrifaは、既存のFreeTypeが担っていたフォントのメタデータ読み込みや字形情報の処理を置き換えるように設計されている。
なぜFreeTypeを置き換えるのか
Chromeではウェブフォントを安全に利用するため、以下のようなセキュリティ対策を講じている。
- フォント処理を、サンドボックス下で実行し、rule of two(1. 入力は信頼できない / 2. 実装する言語は安全ではない)に従って扱う
- フォントを処理する前に、OpenType Sanitizerを用いてフォントを事前に検証する
- フォントの解凍および処理に関わるすべてのライブラリをファズテストしている
しかしながら、Android、ChromeOS、Linux版Chromeなど多数のプラットフォームで使われているFreeTypeがもし脆弱性を含んでいると、膨大なユーザーに影響を及ぼすことになる。FreeTypeは高機能かつ信頼性があるとはいえ、メモリ安全性の面では完全ではなく、脆弱性が潜在的に残りやすい構造となっている。
なぜ問題が紛れ込むのか
FreeTypeのセキュリティを検証する中で、以下のような課題が洗い出された。
1. 安全ではない言語の使用
C/C++などの手動でメモリを管理する言語では、配列やポインタ操作などでバッファオーバーフローなどの脆弱性が生じやすい。
2. プロジェクト固有の問題
パターン/問題 | 例 |
---|---|
マクロによるサイズ指定の不明瞭化 | FT_READ_*やFT_PEEK_*などのマクロが使われており、どの整数型が使われているかが分かりにくい |
新機能追加時のバグ混入 | COLRv1やOT-SVGのサポートを追加した際にも問題が発生している |
テスト不足 | テスト用フォントの作成に手間がかかり、十分な検証が行われていない |
3. 依存関係に起因する問題
FreeTypeが依存するbzip2、libpng、zlibなどのライブラリにも脆弱性が見つかることがある。ファズテストでもこれらの依存先を含めて検証しなければならず、結果として管理負荷が増大する。
4. ファズテストだけでは十分ではない
ファズテストは、多様な入力(ランダムで破損したデータなど)を与えて問題を発見する手法であり、Chromeの安定版出荷前に行われている。しかし、フォントファイルは以下の理由で複雑性が高く、十分なカバレッジを得るのが困難とされている。
- フォントファイルは複数のテーブルを含むコンテナ形式であり、それぞれ文字配置や描画、ヒンティングなど多岐にわたるデータを格納している
- ランダムなビットフリップでは、構造が複雑なフォントファイルの有効な形を作りにくく、多くが早い段階でエラーとして弾かれてしまい深い箇所の検証に至らない
- 複数のテーブル間で相互依存関係があるため、部分的に壊すだけでは狙った動作を引き起こせない
実際、重大度の高い脆弱性が約10カ月間発見されないまま残っていたケースもあり、単なるファズテストでは不十分だと指摘されている。
Skrifaの導入とChromeへの統合
Chromeで使用されるグラフィックスライブラリであるSkiaは、文字の読み込みやアウトラインの描画にFreeTypeを用いている。ここでRust製ライブラリSkrifaを導入し、必要となる処理を代替しようというのが今回の取り組みである。
Rustのサポートは、ChromeセキュリティチームがChromiumプロジェクトにRustを組み込む取り組みを進めたことで実現可能になった。まずはSkiaへの統合が行われ、今後LinuxやChromeOS、さらにAndroid上でのシステムフォント処理もRustベースに移行する予定である。
安全性を最優先に
Chromeチームの最終目標は、メモリアクセスが原因となるセキュリティ脆弱性を極力減らすことである。Rustはunsafeブロックを使用しない限り、メモリ安全性が原則保証される。
ただし、フォントデータを効率よく再解釈するために、bytemuckというライブラリを利用して、バイト列を強い型付けされたデータ構造として再解釈する処理だけはunsafeとして許可している。これは無駄なコピーを回避し、高速にフォントをパースするために必要だという。
将来的には、Safe Transmute Project(※)の成果がRust本体に組み込まれ次第、bytemuckなしでも安全に同様の処理ができるようになると期待している。
※Safe Transmute Projectとは、Rust言語における型再解釈(バイト列を別の型として扱う操作)を、安全に行うための機能を標準化しようとする取り組みである。 Rustには現在、型再解釈のための`std::mem::transmute`という関数が存在するが、これは`unsafe`ブロックを必要とし、誤った使い方をするとメモリ安全性を損なう恐れがある。Safe Transmute Projectでは、この「型再解釈」の処理をコンパイラや標準ライブラリレベルで正しく扱えるようにし、プログラマが`unsafe`を使用せずに安全な型変換を行える仕組みを導入しようとしている。 これにより、Rustにおけるメモリアクセスバグ(バッファオーバーフローや型不整合など)のリスクを下げ、さらに開発者が高速化などの恩恵を受けつつも、安全なコードを書ける環境を整えることを目指している。正しさ(Correctness)の重要性
Skrifaは、独立性の高いコンポーネントに分割され、多くのデータ構造が不変(immutable)として設計されている。これによりコードの可読性や保守性が高まるほか、スレッドセーフな実装が容易になる。加えて、単体テストも容易になり、約700件におよぶユニットテストが用意されている。
また、FreeTypeと同等のレンダリング品質を確保するため、fauntletというツールを活用して、さまざまなフォントファイルに対してSkrifaとFreeTypeの出力を比較検証している。さらに、Chrome統合前にはSkiaの単位でピクセルレベルでの比較テストを行い、さまざまなアンチエイリアシングやヒンティングモードにおいて差異がほとんどないことを確認している。
ファズテストも2024年6月から継続的に実施し、すでに39件のバグが見つかっているが、いずれも深刻なセキュリティ問題には至っていないという。
今後の展望
Rustを使って安全性と開発生産性の両方を向上させられることが確認できたため、今後もChromeのテキスト関連機能をRustベースに拡張していく方針であるという。詳細はOxidizeにもまとめられており、Google Fontsの将来的な計画について言及されている。
詳細はMemory safety for web fonts Blog Chrome for Developersを参照していただきたい。