世界を動かす技術を、日本語で。

Fil-C: メモリ安全なC言語実装

概要

  • Fil-C はC/C++のソースコードを改変せずに メモリ安全性 を実現するコンパイラ
  • 既存アプリケーション のメモリ安全化に特化した「徹底的な互換性」志向
  • 独自ABI・GC・信号安全 など多彩な安全機構を搭載
  • パフォーマンス改善 が進み、実用性が向上
  • Linux From Scratch でのメモリ安全ユーザー空間構築実績あり

Fil-C:C/C++のためのメモリ安全なコンパイラ

  • Fil-C はCやC++のコードを 無改変でメモリ安全 に実行可能にするコンパイラ
  • ポインタ演算、共用体 など従来メモリ安全言語で問題視される機能もサポート
  • fanatically compatible」を掲げ、既存C/C++資産の再利用を重視
  • 開発者は Filip Pizlo 氏一人だが、 Linuxユーザー空間全体 のビルドに成功
  • 信号処理の安全化並行ガーベジコレクタ も実装
  • Clang をベースにしたフォークで、 Apache v2.0 ライセンス(LLVM例外付与)

性能と互換性

  • 初期実装は 著しく遅かった が、最適化により Clang比数倍の遅さ に改善
  • Bash 5.2.32 をFil-Cでビルドし実用テストした結果、体感的な遅延は少ない
  • 内部ABIが独自 のため、Fil-Cでビルドしたオブジェクトは他コンパイラとリンク不可
  • ソースレベル互換性 を維持しつつ、 全てFil-Cで再ビルド が必要
  • Rustなど他言語との相互リンク は未対応

ポインタ管理とInvisiCaps

  • C言語のメモリ安全化 最大の課題は ポインタ管理
  • 当初は 256ビット非スレッドセーフなポインタ を採用していたが、現行は InvisiCaps方式
  • InvisiCaps は「 能力(capability)」と「 アドレス」を分離管理
    • プログラムからは 通常の64ビットポインタ に見える
    • 実際には 補助情報(aux word) を別領域に格納
  • ヒープ確保時 にオブジェクト直前へ 上限値・aux word の2つのメタデータを付加
  • ポインタ書き込み時 に補助領域を確保・能力情報を管理
  • 構造体内ポインタ は実メモリ使用量が 2倍 になる
  • ポインタ読み書きごとに間接参照 が必要なため、 性能は4倍程度低下 (用途に依存)
  • _Atomicやvolatile 対応のため、128ビット領域を活用し アトミック操作 を実現
  • aux word にタグ情報を付加し、関数・スレッド・mmap領域や解放済みオブジェクトの識別も可能

メモリ管理とGC

  • オブジェクト解放時、 aux wordで即時解放 を可能に
  • 本体メモリはGCで遅延解放 し、 use-after-free の検出を徹底
  • 補助情報により正確な到達性解析 を実現し、 Boehm GC より精緻な管理
  • 並行・並列GC を採用し、 スレッド停止時間を最小化
  • safe point でGCとスレッド同期
    • ループ等の 逆方向制御フロー ごとにsafe pointを挿入
    • 通常時はフラグチェックのみ、GC要求時のみコールバック処理
  • signal handler もsafe point到達時のみ実行し、 malloc等もsignal-safe を保証

メモリ安全なLinuxユーザー空間

  • Linux From Scratch (LFS) の手順で メモリ安全なユーザー空間 を構築可能
  • Fil-C自身のランタイムやglibc、カーネル は非Fil-Cでビルドが必要
  • LFSの 第7章まで は通常通り、以降Fil-Cで クロスビルドツール を構築
  • Fil-C付属の 自動化スクリプト でGUIやEmacs等の複雑なアプリもビルド可能
  • 既存Cプログラムのメモリ安全化 において、 実用的なソリューション を提供

適用の検討

  • 未定義動作全般 への対策はないが、 メモリ安全性関連の脆弱性 対策に有効
  • 安定性や成熟度 には課題が残るが、 パフォーマンスより安全性重視 の用途に最適
  • 初期の性能問題で導入を断念した層 も、再評価の価値あり

Hackerたちの意見

議論はないけど、先週のフロントページに載ってたやつ(31ポイント) https://news.ycombinator.com/item?id=45655519

前の議論: 2025 SafepointsとFil-C(87ポイント、1ヶ月前、コメント44件) https://news.ycombinator.com/item?id=45258029

2025 Filの信じられないガーベジコレクター(603ポイント、2ヶ月前、コメント281件) https://news.ycombinator.com/item?id=45133938

2024 Fil-Cマニフェスト: ゴミが入れば、安全なメモリが出る(13ポイント、コメント17件) https://news.ycombinator.com/item?id=39449500

何回も投稿されてるけど、最後に確認したときはx86だけだったよ。

持ち運びできるようにする理由がさらに増えたね。これってLLVMを使って実装できるのかな?

そうだね、テストマトリックスを制限してるから。Fil-Cの設計にはx86_64に縛られる要素はないんだ。x86のメモリモデルにも強く依存してないし、64ビットにも依存してない。もっと多くの貢献者が集まるまで、1つのOSとアーキテクチャに集中して、バグを広いプラットフォームで追跡する余裕を持ちたいんだ。

フィルCか、同じアイデアの別の実装が必要だと思う。Cで書かれたソフトウェアはたくさんあって、それを動かす方法がないと、その知的遺産にアクセスできなくなっちゃうからね。でも、広範なセキュリティの脆弱性があるから、信頼できない入力を扱うソフトウェア、例えばウェブブラウジングやメールのためのCコンパイルに従来の「YOLO」アプローチを使うのは良くないよね。ピズロは、必要なポインターチェックを驚くほど安く実現する方法を見つけたみたいで、もっと勉強したら理解できるといいなと思ってる。(まだ混乱してるのは、InvisiCapsがmemcpyとどう関わるかってところ。)tialaramexは、CプログラマーがフィルCに興奮するとは期待しない方がいいって指摘してる。tialaramexが言ってるポイントは「DWIM」、つまりランダムなメモリにアクセスして定数時間で実行することなんだけど、ほとんどのCプログラマーは4倍のパフォーマンス低下を受け入れたくないと思う。結局、遅い言語を使いたいなら、Cでコードを書いてないだろうしね。でも、興味を探す場所としてはそれは間違ってると思う。フィルCのターゲットはCプログラムのユーザーであって、Cプログラムの作者じゃないから。私たちは、すでに動いているコードベースを安全に使い続けられる利点が欲しいんだ。すべてをRustやTypeScriptに書き直すコストを払わずにね。そして、多くの人にとって、そのパフォーマンス低下は許容できるものかもしれない。

この話題が出るたびにシェアしたくなるんだけど、Appleはブートローダーをコンパイルするためにメモリ安全なCコンパイラ/バリアントを使ってるんだよね。

こちらは、私の理解が正しければ、主流のOSにおいては事実上ユーザースペースアプリケーションに限定されていますね。Fil-Cのウェブサイトの「InvisiCaps by example」ページを読むと、「ポインタとしての整数の洗浄」は禁止されているとのこと。これにより、Fil-Cは低レベルの作業には適さないことになります。Cプログラムのかなりの部分が低レベルの作業に該当するので、これは大きな問題です。(MMIOや事前に割り当てられたメモリのためのint2ptrは理論上は未定義動作ですが、エイリアスルール(C++ではライフタイムルールも)を破らなければ実際には問題ありません。コンパイラが一度は出所を追跡できなくなるためです。)でも、Fil-Cの目的はそこではないですよね。あなたが示唆したように、ユーザースペースアプリケーションの強化に価値があるのです。

Softbound + CETSは、昔好きだった試みの一つだね: https://people.cs.rutgers.edu/~santosh.nagarakatte/softbound... CCuredも別の例だね: https://people.eecs.berkeley.edu/~necula/Papers/ccured_popl0...

Fil-CをNix用にパッケージングしているところだよ。それに、Fil-CをツールチェーンとしてNixに統合して、Fil-Cを使ってどんなNixパッケージでもビルドできるようにしてるんだ。 https://github.com/mbrock/filnix ちゃんと動いてるよ。tmuxやnethack、coreutils、Perl、Tcl、Lua、SQLite、他にもいろいろビルドできる。バイナリキャッシュは https://filc.cachix.org にあるから、Clangフォークのビルドを40分待たなくて済むよ。 64ビットのLinuxコンピュータでフレークを使ったNixがあれば、今すぐに「nix run github:mbrock/filnix#nethack」を実行できるよ!

それはめっちゃワクワクするね!ありがとう!

それはめっちゃクールだね!おそらく、テストボックスで試してみるつもりだよ。Pythonはどう動くの?もちろん、filc.pythonをシステムに追加するだけでいいけど、python3 -m pip install whateverを実行したら、CモジュールはFil-Cコンパイラで再ビルドされるのかな?

そうだね、nixはFil-Cを簡単に適応できる最初のものの一つになると思う。すでに異なるパッケージが完全に独立している形でパッケージングされてるから、Fil-CのABI互換性はあまり関係ないんだ。他のターゲットは、パフォーマンスの影響やソース互換性の問題があまり気にならない企業向けディストリビューションが多いと思うし、メモリの安全性は絶対に重要だよね。Fil-Cでコンパイルされたフラットパックも、普通のデスクトップユーザーには面白いターゲットになるかも。(例えば、ブラウザを動かすとか)Fil-Cの世界でGPUグラフィックスは可能なのかな?もしかしたら、Mesaスタック全体がFil-Cでコンパイルされてる場合だけかも。そうなると、GPUの使用はオープンドライバーに制限されるね。

ちょっと関連するけど、安全なC++の提案が続かないみたいだね。 https://news.ycombinator.com/item?id=45234460

要約すると:通常のケースで4倍遅くなる。このアプローチのパフォーマンスオーバーヘッドのせいで、ほとんどのプログラムが約4倍遅くなる。

要約すると:通常のケースで4倍遅くなる 4倍遅いのは通常のケースじゃないよ。4倍ってのはオーバーヘッドの上限に近い数字だね。

Cとメモリ安全性を読むたびに、Golangを思い出すんだ。特にユーザースペースでは。

Goプログラムは、複数のスレッドを使うと完全にメモリ安全じゃないよ。ファットポインタとのデータレースの可能性があるからね。 https://news.ycombinator.com/item?id=44672003

インフェルノみたいな感じだけど、今のリムボはJITじゃなくてAOTコンパイルされてるよ。ただ、Goにはカーネルのような商業プロジェクトもあって、関連するTamaGoフォークが最終的にリファレンス実装に取り込まれるかもしれないね。 https://www.withsecure.com/en/solutions/innovative-security-...

すごいプロジェクトだね。いくつか質問があったけど、自分なりに答えを見つけたと思う(pizlonator、間違ってたら訂正してね):

  1. 誤ったアドレスからの読み書きを防ぐにはどうすればいいの? 答え:アライメントがずれたポインタの読み書きはトラップされるから、これは単純に許可されてないよ。
  2. ポインタを通じてポインタを保存するのはどう実装されてるの?(例えば *(char **)p = s の場合)ランタイムは *p が「フライト」か「ヒープ」かをチェックする必要があるの? 答え:いいえ。アドレスが取られたフライト(つまりローカル)ポインタは、隣接する2つのワードとして実装されるわけじゃなくて、コールフレームはヒープオブジェクトと同じオブジェクトレイアウトで割り当てられるよ。フライトポインタはその「intval」で、ペアの「lower」は「aux」割り当ての同じオフセットにある(おそらくフレームの一部としても割り当てられてる?)。
  3. リターン後の使用エラーはどう防ぐの?ローカルポインタをグローバル変数に保存してからリターンしたら、後で新しい関数を呼んで元のフレームを上書きしたら、誤った lower を取得することにならない? 答え:いいえ。コールフレームはGCによって割り当てられるから、通常のCスタックとは違うよ。グローバル参照がコールフレームを生かしておくことになるんだ。それに続くプログラムは、絶対に動かないはずなのに、実際には動く。 ~すごい~ 信じられない:
#include <stdio.h>
char *bottles[100];
__attribute__((noinline)) void beer(int count) {
    char buf[64];
    sprintf(buf, "%d bottles of beer on the wall", count);
    bottles[count] = buf;
}
int main(void) {
    for (int i=99; i >= 0; i--)
        puts(bottles[i]);
}

うーん…ここには危険があるね。Fil-Cでコンパイルしたプログラムをテストして、「普通の」コンパイラでも安全だと思っちゃう人が出てきそう。未定義の動作をフラグ付けするオプションがあればいいな。

すごい努力だけど、全体のアイデアには少し欠陥があると思う。スピードが必要なら、このC実装は使えないよ。何倍も遅いからね。スピードが重要じゃないなら、メモリ安全な言語を使えばいいじゃん?両方が重要なら、Rustを使えばいいのに。Fil-Cを使ってCで書かれた既存のソフトウェアを再コンパイルするのもあまり良いアイデアじゃないよ。少なくともFil-Cの使用で見つかったバグを修正するために何らかの変更が必要になるだろうし。バグが修正された後も、なぜFil-Cを使い続けるの?

ほとんどのソフトウェアは、Fil-Cで修正なしに問題なく動くし、パフォーマンスもそんなに悪くないよ。セキュリティがパフォーマンスより重要なアプリケーションもあるし(例えば軍事用途とか)。