/note/tech

Rust に書き直さなくても C 言語をメモリ安全にできる Fil-C を試した

要約:

■ 1. 背景: 言語移植とメモリ安全性をめぐる議論

  • BunのJavaScriptランタイムがZigからRustへ移植された(理由の一つはメモリ安全性由来のバグ)
  • ZigのAndrew Kelley氏が反論:問題は言語ではなく開発プラクティスにある、レビューされていない生成コードが安全とは言えないと指摘
  • 逆方向の例として、RocコンパイラがRustからZigへ移植され、インクリメンタルビルドが3.4秒から35msに改善、メモリ破壊バグはZig版の方が少なかった
  • 「メモリ安全のために言語を乗り換えるべきか」の議論は決着しておらず、どちら向きの移植にも莫大なコストがかかる
  • Kelley氏がFil-Cにインスパイアされたメモリ安全コンパイルモードをZigのissueに提案し承認された(コードではなくコンパイラとABIでメモリ安全を実現する方針)

■ 2. Fil-C の概要

  • 開発者:
    • Filip Pizlo氏(Epic Gamesシニアディレクタ、元Apple WebKit/JavaScriptCore開発者)が開発
  • 構成:
    • clang 20.1.8ベースのコンパイラと専用ランタイムで構成
    • C17およびC++20に対応
  • 互換性:
    • "fanatically compatible(狂信的に互換)"を謳い、既存のC/C++コードをほぼ変更なしでコンパイル可能かつメモリ安全化
    • OpenSSL、CPython、SQLite、OpenSSH、Emacsなど多数のソフトウェアが動作確認済み
    • Fil-CのみでビルドされたLinuxユーザランド「Pizlix」も存在
  • 設計方針:
    • Rustのunsafeに相当する抜け道が存在しない
    • AddressSanitizerのような「バグ検出ツール」ではなく「防御機構」として設計
    • タグベースのASanやMTEと異なり、ケーパビリティベースで攻撃回避が困難
    • CHERI相当の安全性を通常のx86_64上で実現

■ 3. 技術的な仕組み

  • InvisiCaps:
    • メモリ上の全ポインタに、Cのアドレス空間からは見えないケーパビリティ(下限・上限・状態)を対応付け
    • LLVM IRレベルの全基本操作をケーパビリティに対して検査
  • FUGC(Fil's Unbelievable Garbage Collector):
    • 並行ガベージコレクタ
    • free()はオブジェクトを「free状態」にマークするだけで、実際のメモリ回収はGCが行う
    • use-after-freeが原理的に攻撃に利用できない構造
  • エラー処理:
    • 検査違反は全て「Fil-C panic」として捕捉され、スタックトレース付きでプロセスが停止
    • ヒープ・スタックの範囲外アクセス、use-after-free、型混同、va_list誤用、システムコールバッファ検査などをカバー

■ 4. インストールと基本的な使い方

  • 現時点でLinux/x86_64のみサポート
  • GitHubのリリースページからバイナリを取得し、setup.shを実行するだけでインストール完了
  • patchelfが必要(事前にインストールが必要)
  • build/bin/clangを通常のclangと同様に使用可能、libcにはmuslを使用

■ 5. メモリ安全性の動作例

  • バッファオーバーフロー:
    • スタックバッファオーバーフローを試みると、上限超え書き込みとして検出されFil-C panicが発生
    • 発生箇所のファイル名・行番号をスタックトレース付きで表示
    • libcのstrcpyもFil-CでビルドされているためライブラリAの深い場所のオーバーフローも捕捉可能
  • use-after-free:
    • free済みオブジェクトへのアクセスを検出してパニック
    • GCが「ポインタが本当に無くなってから」メモリを回収するため、解放後メモリが別オブジェクトに再利用される攻撃が起きない
  • ケーパビリティの確認:
    • stdfil.hの独自ヘッダを通じてポインタに付いたケーパビリティ(値・下限・上限)を確認可能
    • ポインタ演算で値が動いても下限・上限は変わらず、freeすると上限が下限まで潰されfreeフラグが付く

■ 6. パフォーマンス比較

  • 比較環境:
    • Ryzen 7 7735HS上のWSL2
    • gcc 13.3 / clang 18.1 / Fil-C 0.681 / rustc 1.95.0-nightly / zig 0.15.2 / go 1.26.1
  • ベンチマーク種別と結果(gcc比):
    • mandel(浮動小数点中心): Fil-Cは約1.05倍でオーバーヘッドは誤差の範囲
    • sieve(配列アクセス中心): Fil-Cは約2.3倍で境界チェックのコストが顕在化
    • btree(アロケーション・ポインタ辿り中心): Fil-Cは約1.1倍で予想外に健闘、Zigの ReleaseSafeより速い
  • btreeの考察:
    • 並行GCによりfree()が即座にメモリを返さない設計がアロケーション多発時に有利に作用
    • 最速はGoの0.65倍で、トレーシングGCが死んだオブジェクトに触れずまとめて回収できるためと考えられる
    • Fil-CのFUGCも同様の恩恵を受けている可能性が高い
  • 全体的な見通し:
    • 実アプリケーションではポインタ辿りの多さ次第で1〜6倍程度
    • ASanと異なり本番投入を前提に最適化が継続されており、バージョンを追うごとに改善

■ 7. 現時点の制限

  • 動作プラットフォームはLinux/x86_64のみ(以前はmacOS/ARM64でも動作していたため原理的な制限ではない)
  • 通常の方法でビルドされた既存バイナリ(.so)とはリンク不可
  • 依存ライブラリを含め全てFil-Cでビルドする必要がある(これは設計思想であり、推移閉包まで含めた完全なメモリ安全性の裏返し)

■ 8. まとめ

  • C/C++を書き直さずにそのままメモリ安全にするFil-CのアプローチはC/C++資産を持つ開発者にとって魅力的
  • 再コンパイルするだけでバッファオーバーフローからuse-after-freeまで防止可能
  • ZigがFil-C方式を取り込もうとしており、他の言語にも波及する可能性がある
  • パフォーマンスが最重要でないネットワーク境界のツールやパーサなどへの適用が有望