背景
近年、「メモリ安全性(memory safety)」は非常に注目を集めるテーマとなっている。しかし、「メモリ安全性」とは一体何を指しているのか、その目的が何であるのかを明確にすることがまず重要である。たとえば、Clang Static Analyzer や rustc などのコンパイラベースの静的解析を活用して、コンパイル時に潜在的な問題を検出しようとしているのか。それとも、コードの正しさをコンパイラに任せ、Go や Java のように実行時のガーベジコレクション等のメカニズムによって問題を解決しようとしているのか。あるいは、究極的な目的として「攻撃からシステムを守る」というセキュリティ強化だけを重視しているのか。
メモリ安全性の問題が複雑であるのは、セキュリティそのものの性質を反映している。セキュリティとは計算機科学と複雑性理論が交わる学際的な分野であり、完全に理解・制御するのは非常に困難である。そのため、既存ソフトウェアを一部またはすべて「メモリ安全な言語」で書き換えれば全ての脆弱性がなくなる、という考え方は現実的ではない。
プログラミング言語は、ガーベジコレクションや配列境界の検査といったメモリ安全性を考慮した仕様を備えて設計されていることがある。だが、実際には各言語の実装によってその戦略は異なり、要件や性能のトレードオフにより安全性が犠牲になる場合もある。たとえば、Lisp は通常ガーベジコレクタを持ち、柔軟なデータ操作や動的型付けを特徴とするが、だからといって全ての Lisp 実装がメモリ安全であるとは限らない。パフォーマンスやその他の要件を優先する中で、一部の安全検査が無効化されれば、バッファオーバーフローや不正なポインタアクセスなどの脆弱性は依然として起こり得る。
同様に、C/C++ は長らく「メモリ安全でない言語」と見なされてきた。なぜなら、プログラマが自由にメモリ操作やポインタ演算を行えるからである。しかし、静的解析ツール、厳密なコードレビュー、実行時の検出メカニズムなど、厳格なエンジニアリングを導入すれば、C/C++ によるコードも特定の環境ではほぼバグのない状態に近づけることができる。
本記事では、HardenedLinux が過去数十年にわたり、いかにしてシステムの複雑性を抑え、メモリ安全性を高めるための手法を実践してきたのかについて紹介する。メモリ安全性は、コンパイラだけ、または実行時だけで完結するものではない。言語設計、ツールの支援、エンジニアリング実践の三者が協調することで、初めて「攻撃に強いシステム」への道が拓かれる。
なお、本稿では、Mandatory Access Control、サンドボックス化、Linuxカーネルのハードニング、あるいは DMA 攻撃によって Lisp マシンをハックするような内容には触れない。
本番環境向けの GNU/Linux ディストリビューション選定
HardenedLinux の初期メンバーは、いくつかの商用 Linux ディストリビューションの出身者で構成されていたため、「GNU/Linux ディストリビューションを構築すること」自体は、それほど難しいものではないと理解していた。しかし、それを長期的に保守し、かつ安定性を維持するには、はるかに高い水準が求められる。
最も重要なのは、中核となるソフトウェアやライブラリ群が、専門的なスキルを持つメンテナによって管理されていることである。バグ修正やセキュリティ脆弱性への対応において、単に問題を上流プロジェクトへ丸投げするのではなく、選択肢を慎重に比較し、多くの場合は「バックポート」という手法を選び、機能と安定性の両立を図る必要がある。
これらの観点から、HardenedLinux のベストプラクティスは基本的に Debian をベースとしている。当時、コミュニティ主導でありながら、極めて高いスキルを持つメンテナが揃っている唯一のディストリビューションだったからである。
ベースディストリビューションが決まった後、次に直面する課題は、「メモリ安全性のない言語」(C/C++ など)で実装されたアプリケーションおよび依存ライブラリに内在する品質・セキュリティリスクへの対応である。サニタイザやファザーが広く普及する前は、大規模なバグハンティングには膨大な手作業が必要だった。しかし現在では、サニタイザを有効にし、定期的なリグレッションテストを実施するだけで、QA(品質保証)の段階で大半の問題を検出できるようになってきている。さらに、広範囲なファジング(Fuzzing)テストを導入すれば、潜在的な脆弱性の網羅的な検出も期待できる。これは GNU/Linux ディストリビューションのレベルでも適用可能であり、実際、コンパイラや C ランタイム(glibc、musl 等)など一部の例外を除いて、ほとんどのコンポーネントに対して実施できる。
また、HardenedLinux では数年をかけて状態ベースの Linux カーネルファザーを開発し、2020 年には Google の Syzkaller に「カバレッジフィルター」機能を upstream に寄与した。これは、アジアの OSS コミュニティから生まれた数少ない影響力ある貢献の一つだと考えている。
ファジングとサニタイザを組み合わせた技術アプローチは、メモリ安全性を強化する古典的な手法である。たとえば、Linux カーネルの場合、用途ごとに依存するサブシステムが異なる。ストレージサーバはファイルシステムを重視し、ネットワークサーバはネットワークプロトコルスタックに依存する。これに対応するため、状態モデルに基づいたファジングツール「VaultFuzzer」も開発された。このツールにより、特定のターゲットシステムに対して集中的なストレステストが可能となった。たとえば、32コア CPU・64GB メモリ構成のシステムでは、約20時間でネットワークプロトコル実装の主要部分に対して約72%のコードカバレッジを達成できる。
もちろん、残り28%にメモリ安全性のリスクが残っているという批判はある。しかし、我々のアプローチでは、システム全体のセキュリティアーキテクチャの設計を通じて、カーネルレベルでのランタイム緩和策を導入し、このギャップを埋めることを目指している。この点については後述する。
脆弱性のライフサイクルにおける各フェーズ
Pre-exploitation フェーズ(バグのトリガー段階)
Exploitation フェーズ(実際の悪用段階)
Post-exploitation フェーズ(例:rootkit の植え付けなど)
脆弱性のトリガー(バグの発動)段階は、「Pre-exploitation(事前利用)」フェーズと見なされます。
特にフリー/オープンソースソフトウェア(FOSS)プロジェクトにおいては、この段階での問題は理想的には QA(品質保証)プロセスで検出・対応されるべきです。
では、サニタイザやファザー以外に、どのような手法で脆弱性を発見できるのでしょうか?
ここで、我々が注目しているオープンソースプロジェクト Fil-C の価値が明らかになります。
Fil-C は、Epic Games によって開発された C/C++ 向けのメモリ安全性強化ツールであり、非常にラディカル(先鋭的)なアプローチを採用しています。
Fil-C の主な特徴は以下の通りです:
Clang/LLVM コンパイラをカスタマイズし、コンパイラライブラリ全体を改良
各種トランスフォーメーションパスや C ランタイム(例:musl)に対応
一部のライブラリやアプリケーションには最小限のコード修正が必要
このように、Fil-C はコンパイラレベルでのアプローチを通じて、Pre-exploitation 段階でのバグ発見率を飛躍的に向上させる可能性を秘めています。
Bug Type | Sanitizers | Fil-C | Clang Bounds-Safety |
---|---|---|---|
0-out-of-bounds-access.c | YES (ASAN) | NO | NO |
2-out-of-bounds-access.c | YES (ASAN) | YES | NO |
3-out-of-bounds-in-bounds.c | YES (ASAN) | YES | NO |
1-overflowing-out-of-bounds.c | NO (ASAN) | YES | N/A |
4-bad-syscall.c | NO (ASAN) | YES | N/A |
5-type-confusion.c | NO (ASAN) | YES | N/A |
6-use-after-free.c | YES (ASAN) | YES | N/A |
7-pointer-races.c | YES (ASAN/TSAN) | Partially | N/A |
8-data-races.c | YES (TSAN) | NO | N/A |
Fil-Cと従来のサニタイザの検出能力を比較することで、Fil-Cはすべての問題を解決できるわけではないものの、「エクスプロイト可能性」の観点からC/C++のメモリ安全性を実現しており、一般的な脆弱性を無害化することに成功していることが分かります。興味深いのは、Fil-Cが問題の発見を助けるだけでなく、重要なコンポーネントをFil-Cでコンパイルして、独立したランタイムとして使用することも可能であるという点です(もしかすると、Epic Gamesの次世代タイトルにその成果が反映されるかもしれません)。もちろん、Fil-Cの性能やエンジニアリングの成熟度は、まだ一般的な採用には至っていませんが、非常に有望な方向性を示しています。
エクスプロイト vs 緩和策
バグの発見プロセスを論じたのであれば、それに対する緩和策についても検討することが重要です。もし「サニタイザ+ファザー」のモデルではすべてのバグを発見できず、ターゲットのプログラムが性能面の理由からFil-Cのようなメモリ安全なC/C++ソリューションを採用できない場合、次に考えるべきは、コンパイラやCランタイムが提供する一連の緩和技術です。
これらの緩和策には、NXやCET、BTIなどのハードウェアレベルの実装に加えて、多くのソフトウェアレベルの実装も含まれています。これらのソフトウェア保護機構は軽視すべきではなく、重要な局面では決定的な役割を果たす可能性があります。2007年の「Attacking the CORE」によって焦点がカーネルに移ってからも、ユーザー空間のプログラムに対しても多くの防御策が必要であることを、多くの事例が示しています。
最近では、ret2氏によるレポートにおいて、Pwn2Own Ireland 2024大会でSynology DiskStation DS1823xs+に対してRCE(リモートコード実行)を成功させた手法が紹介されました。この時、もしSynologyのセキュリティチームがFULL RELROやCFIを有効にしていたならば、防御力は格段に高まり、攻撃の難易度も大幅に上昇し、結末も違ったものになっていたかもしれません。
以下は、私たちがよく参照する緩和策のリストです:
Bug Type | Sanitizers / Compiler Options |
---|---|
Stack canary | -fstack-protector-* |
Shadow stack |
-mshstk (GCC), -fsanitize=safe-stack (Clang) |
Fortified source | -D_FORTIFY_SOURCE=3 |
Full ASLR with PIE | -pie |
Control flow integrity |
x86: -fcf-protection= , arm64: -mbranch-protection= , Software-based |
Relocation only | -Wl,-z,now |
Bounds check |
-XClang -fexperimental-bounds-safety (Clang) |
行動の呼びかけ(Call for Action)
近年、オープンソース開発者やセキュリティ研究者との議論の中で、「Googleでさえ事前にバグハンティングしているのに、多くの脆弱性が見逃されている。だったら、すべてのソフトウェアとライブラリをメモリ安全な言語で書き直すべきではないか?」という意見を何度も耳にしてきました。
一見もっともらしく聞こえるかもしれませんが、この考え方は他の事実と矛盾しています。数ヶ月前、南欧のセキュリティエンジニアと話をしていたとき、彼は「HSM(ハードウェアセキュリティモジュール)関連のオープンソース暗号ライブラリが今でもセグメンテーションフォールトを起こす」と不満を漏らしていました。私は「デバッグ/テストビルドでサニタイザを有効にしていないのか?」と尋ねましたが、残念ながらそのライブラリにはサニタイザが統合されていないことが確認されました。誤解しないでください、それは OpenSSL ではありません。OpenSSL は以前からサニタイザを統合しており、すべてのリグレッションテストが通るように設計されています。
「Google ですら完璧にできないなら、全部書き直すしかない」という論理は、ある意味で無責任です。0ldsk00l ハッカーやサイファーパンクの視点から見れば、自立性(self-sovereignty)は極めて重要です。Google が修正しきれていないからといって、私たちが無視してよい理由にはなりません。放っておくな、自分でクソを片付けろ。結果的に、それがコミュニティ全体の利益にもつながるのです。
したがって、すべてのフリー/オープンソースソフトウェアの開発者たちへ呼びかけます:少なくとも、デバッグビルドやテストビルドではサニタイザを有効にしてください。
みんながそれを実践するだけで、世界をより安全にするための最も低コストな方法になるのです。
ソフトウェアの書き直しについて(Rewriting Software)
ソフトウェアやライブラリをメモリ安全な言語で書き直すというのは、非常にコストの高い作業です。それでも真剣にその方向を検討しているのであれば、ぜひ Lisp/Scheme での書き直しを検討してみてください。
技術参考文献
CC Bounds Checking Example https://williambader.com/bounds/example.html
The Fil-C Manifesto: Garbage In, Memory Safety Out! https://github.com/pizlonator/llvm-project-deluge/blob/deluge/Manifesto.md
Exploiting the Synology DiskStation with Null-byte Writes: Achieving remote code execution as root on the Synology DS1823xs+ NAS https://blog.ret2.io/2025/04/23/pwn2own-soho-2024-diskstation/
memory-safety-coverage-test https://github.com/hardenedlinux/memory-safety-coverage-test
Technical analysis of syzkaller based fuzzers: It’s not about VaultFuzzer! https://hardenedvault.net/blog/2022-08-07-state-based-fuzzer-update/