
2025/12/14 7:58
Linux Sandboxes and Fil-C
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
(欠落している詳細を組み込む)
メモリ安全性とサンドボックスは、相補的な方法でソフトウェアを保護します。メモリ安全性はバッファオーバーフローを防ぎ、サンドボックスはプログラムのシステムアクセスを制限します。あるプログラムは片方だけを持つことも可能ですが、両者が併用されると最強の保護になります。Fil‑C は、実行時チェックを挿入して syscall の制限とスレッド分離を強制することで、メモリ安全な C/C++ コードを既存のサンドボックス手法と統合する方法を示しています。
純粋な Java プログラムは、メモリ安全であってもファイル安全ではありません。これは OS のフル権限で実行されるためです。一方、低レベルのアセンブリコードはバグがあってもファイル安全になることがあります。欠陥のあるカーネル呼び出しが攻撃者にサンドボックスを抜けさせる可能性があるためです。この事実は、サンドボックスが不完全である理由を示しています。メモリ障害時に攻撃者が悪用できる特権「ブローカー」呼び出しを許可してしまうからです。
Fil‑C は OpenSSH の現在のサンドボックス(seccomp‑BPF、chroot、setrlimit、およびユーザー/グループ分離)に基づいて構築されています。Fil‑C を OpenSSH に適応するには、2 つの小さな変更だけで済みます ―
SECCOMP_RET_KILL_PROCESS をすべてのスレッドが違反時に終了するように変更し、MAP_NORESERVE と sched_yield を mmap の許可リストに追加します。ランタイムハンドシェイク(filc_runtime_threads_handshake)は、各スレッドが必要な PR_SET_NO_NEW_PRIVS および PR_SET_SECCOMP 設定を受け取ることを保証します。複数のユーザー スレッドが存在する場合、Fil‑C は安全性エラーを報告します。
Fil‑C を統合すれば、OpenSSH では手動でサンドボックス構成を行う必要が少なくなり、メモリバグによる攻撃に対するハードニングが簡素化されます。同じアプローチは、現在別々のサンドボックスツールに依存している他の低レベルサービスにも有益であり、設定を合理化し全体的なセキュリティを強化します。
本文
メモリ安全性とサンドボックスは別々の概念です。
それらは直交的に考えることができます:プログラムは「メモリ安全だがサンドボックスされていない」場合もあれば、逆に「サンドボックスされているがメモリ安全でない」ケースもあります。
例
メモリ安全だがサンドボックスされていない
ユーザーが指定したファイル名を読み書きする純粋な Java プログラム。
OS は、ユーザーがアクセスできる任意のファイルを書き換えることを許可します。
プログラム自体はメモリ安全であっても危険です。Java 実装にバグがあり、メモリ安全性を突破した場合、攻撃者はコード実行経由で任意のファイルを書き込むことができます。
サンドボックスされているがメモリ安全でない
純粋な計算だけを許可し、すべての能力(capability)を剥奪したアセンブリプログラム。
ファイルを開くか書き込もうとすると、カーネルは先に行われた capability 削除によりプロセスを強制終了します。
メモリ安全性の欠陥があっても、サンドボックスを突破しなければファイルを書き換えることはできません。
実際、サンドボックスには意図的な穴があります。典型的なサンドボックスでは特権付きブローカーと通信することが許可されます。攻撃者はメモリ安全性の欠陥を利用して悪意あるメッセージを送信し、ブローカーを乗っ取る可能性があります。
最適な防御は「サンドボックス + メモリ安全性」の組み合わせです。
この文書では、OpenSSH が使用する seccomp‑ベースの Linux サンドボックスコードを、メモリ安全で C/C++ を実装した Fil‑C へ移植する方法について説明します。Fil‑C は syscall 境界まで安全性を保証し、init や udevd のような低レベルシステムコンポーネントでも利用できます。
背景
Fil‑C は C/C++ をメモリ安全に実装したものです。
他の多くのメモリ安全言語とは異なり、Fil‑C は「コードが Linux の syscall と接触する地点」で安全性を強制します。
Fil‑C ランタイムは低レベルコンポーネントにも耐えうるほど堅牢で、OpenSSH を含む多くのプログラムが Fil‑C の seccomp‑BPF サンドボックスを利用しています。
本稿では、OpenSSH が Linux 上で非特権
sshd セッションプロセスをサンドボックス化するために使用している seccomp 及び関連技術について説明します。主なツールは以下の通りです。
- chroot – プロセスのファイルシステムビューを制限
- sshd ユーザー/グループで実行 – アカウントに権限を与えない
- setrlimit – ファイルオープン、プロセス起動、書き込みを禁止
- seccomp‑BPF syscall フィルタ – 正当な syscall のみ許可し、その他は
で終了SIGSYS
Chromium と Mozilla は seccomp を使った Linux サンドボックスに関する優れたノートを公開しています。Fil‑C は chroot、ユーザー/グループの設定が簡単です。これらの syscall は Fil‑C によって自動的に許可されます。
setrlimit と seccomp‑BPF は、Fil‑C ランタイムがスレッドを生成しメモリを確保し同期するため、特別な注意が必要です。本稿では、Fil‑C でこれらのサンドボックス技術を有効に活用する方法を解説します。
Fil‑C ランタイムでスレッド作成を防ぐ
Fil‑C ランタイムはガベージコレクション用にバックグラウンドスレッドを生成し、アイドル時には自動的に停止します。プログラムが再びメモリを確保すると、これらのスレッドが再起動します。
しかし新しいスレッドを作成することは、OpenSSH の
setrlimit サンドボックスで規定されている「新プロセス禁止」ルールに違反します(Linux ではスレッドは軽量プロセスとして扱われます)。さらに、clone3 などの syscall は OpenSSH の seccomp アロウリストに含まれていません。プロセス作成を許可するとサンドボックスが弱体化します。
そこで
<stdfil.h> に新しい API を追加しました。
void zlock_runtime_threads(void);
zlock_runtime_threads() は、必要なすべてのランタイムスレッドを即座に起動し、以降は自動シャットダウンを無効化します。OpenSSH の
ssh_sandbox_child() で setrlimit や seccomp‑BPF を適用する前に呼び出されます。
OpenSSH サンドボックスへの調整
zlock_runtime_threads() が後からのスレッド作成を防ぐため、OpenSSH のサンドボックスはほぼそのままで済みました。seccomp フィルタには以下の変更が加えられています。
- 失敗時に
を返すようにし、Fil‑C バックグラウンドスレッドも違反で終了させます。SECCOMP_RET_KILL_PROCESS - Fil‑C アロケータが使用する
を mmap のアロウリストへ追加しました。これにより攻撃者に新たな権限は与えられません。MAP_NORESERVE
を許可しました。これは意味的には no‑op で、Fil‑C ランタイムのロック実装で使用されます。sched_yield
その他の変更は不要です。フィルタは同期に必要なすべての futex syscall を既に許可しています。
Fil‑C が prctl
を実装する方法
prctlOpenSSH は seccomp フィルタを次の 2 つの
prctl コールでインストールします。
/* PR_SET_NO_NEW_PRIVS */ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { debug("%s: prctl(PR_SET_NO_NEW_PRIVS): %s", __func__, strerror(errno)); nnp_failed = 1; } /* PR_SET_SECCOMP */ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &preauth_program) == -1) debug("%s: prctl(PR_SET_SECCOMP): %s", __func__, strerror(errno)); else if (nnp_failed) fatal("%s: SECCOMP_MODE_FILTER activated but " "PR_SET_NO_NEW_PRIVS failed", __func__);
これらの呼び出しは、呼び出しスレッドにのみ影響します。Fil‑C のバックグラウンドスレッドが
no_new_privs フラグと seccomp フィルタを持たないままだと、メモリ安全性を突破した攻撃者がそれらのスレッドでサンドボックスを回避できる恐れがあります。
これを防ぐために Fil‑C は
prctl のラッパーを用意し、すべてのランタイムスレッドへハンドシェイクします:
/* すべてのランタイムスレッドでコールバックを呼ぶ。 */ PAS_API void filc_runtime_threads_handshake(void (*callback)(void* arg), void* arg);
コールバックは各ランタイムスレッド上で要求された
prctl を実行し、no_new_privs フラグと seccomp フィルタが全スレッドに適用されることを保証します。
複数のユーザー・スレッドを扱う際の曖昧さを回避するため、これら 2 つの
prctl コマンドは、プログラムが 1 以上のユーザー・スレッドを持っている場合に Fil‑C の安全性エラーを発生させます。
結論
メモリ安全性とサンドボックスを併用することで、最も強固な保護を実現できます。
本稿は、Fil‑C と Linux のサンドボックス技術を組み合わせる方法を示し、サンドボックスのセキュリティレベルや Fil‑C のメモリ安全性保証を低下させずに統合する手順を解説しました。