xz-utils バックドアの隠れたエンジン:なぜ GNU IFUNC はセキュリティ上のリスクとなるのか

CVE-2024-3094、すなわち xz-utils のバックドアの発見は、サイバーセキュリティコミュニティに衝撃を与えました。事後分析の多くはソーシャルエンジニアリングやソフトウェアサプライチェーンの侵害に焦点を当てていますが、バックドアが OpenSSH を乗っ取ることを可能にした技術的メカニズムは、しばしば見落とされがちです。xz-utils のようなライブラリが、どのようにして SSH サーバーへの root 権限を付与し得たのかを理解するには、悪意のあるコードの先にある、Linux エコシステムにおける根本的なアーキテクチャ上の決定を調査する必要があります。

この脆弱性の核心には、ディストリビューション固有のパッチと、GNU IFUNC (Indirect Functions) として知られる GNU C Library (glibc) の強力かつ難解な機能の組み合わせがあります。IFUNC は、リンカーが main 関数が始まる前に任意のコードを実行することを許可することで、攻撃者にとって完璧な隠密性の高いエントリーポイントを提供しました。

依存関係の連鎖

xz-utils がどのようにして OpenSSH のアドレス空間に紛れ込んだのかを理解するには、Linux ディストリビューションによって異なる、複雑な依存関係の網を辿る必要があります。

  1. OpenSSH の分離: OpenSSH は主に OpenBSD 向けに開発されています。Linux で動作させるために、「Portable OpenSSH」プロジェクトがパッチセットを提供しています。
  2. ディストリビューションによるカスタマイズ: Fedora や Debian などの一部のディストリビューションは、sshd の再起動時における特定のレースコンディションを解決するために、systemd と統合させるための追加のパッチを OpenSSH に適用しています。
  3. 依存関係の漏洩: これらのパッチ適用済みの OpenSSH は libsystemd に依存しており、libsystemd はさらに xz-utils に依存しているため、xz-utils ライブラリは SSH デーモン (sshd) のメモリ空間にロードされます。

この連鎖は重大な脆弱性を生み出しました。高権限プロセス (OpenSSH) が、プロセスの実行フロー自体を修正できるメカニズム (IFUNC) を含むライブラリ (xz-utils) をロードしていたのです。

GNU IFUNC の理解

GNU IFUNC は、プログラムが実行時にどのバージョンの関数を使用するかを決定できるように設計されています。これは、主に CPU の最適化に使用されます。例えば、プログラムが計算を実行する必要がある場合、IFUNC リゾルバを使用して現在の CPU が AVX2 命令をサポートしているかを確認できます。サポートしている場合は、高度に最適化された AVX2 バージョンの関数のポインタを返し、そうでない場合は汎用的な実装のポインタを返します。

これはパフォーマンスの最適化のように聞こえますが、技術的な実態は、IFUNC が動的リンクプロセス中に任意のコードの実行を許可することにあります。研究で示された tty_demo.c の例のように、IFUNC リゾルバは、プログラムがエントリーポイントに到達する前に、STDOUT がターミナルであるかを確認するなど、プロセスの環境を調査するために使用できます。

なぜ IFUNC はセキュリティ上のリスクとなるのか

セキュリティの観点から、IFUNC はいくつかの決定的な失敗を招きます。

1. RELRO の無効化

RELRO (Relocation Read-Only) は、攻撃者による Global Offset Table (GOT) の書き換えを防ぐために設計されたセキュリティ機能です。しかし、IFUNC は、選択された関数実装のポインタをリゾルバが書き込む必要があるため、GOT を書き込み可能にする必要があります。GOT がまだ書き込み可能である間に任意のコードを実行させることで、IFUNC は解決フェーズにおいて事実上 RELRO を無効化してしまいます。

2. 「最小驚愕の原則」への違反

ほとんどの開発者は、共有ライブラリをロードすること自体は受動的な行為であると考えています。IFUNC は、ライブラリのロードという行為を能動的な実行イベントに変えることで、この想定を裏切ります。ある研究者が指摘したように、動的ライブラリをロードすることが、まさにそのライブラリを保護するために設計された安全機能そのものを侵害することになるとは、まともな人間は誰も予想していません。

3. 複雑さと脆弱性

IFUNC は実装とドキュメント化が非常に困難であることで知られています。GCC 開発者は以前、このインターフェースを「間違い」と表現しており、glibc の解決策が堅牢にするためには、他のユーザーが普遍的に利用できるものではないため、脆弱性や予期せぬクラッシュを招くと指摘しています。

より良い代替案はあるのか?

IFUNC の批判者は、パフォーマンスの向上は微々たるものであり、より安全な代替案が存在すると主張しています。

  • グローバル関数ポインタ: 開発者は main 関数の開始時に命令的に正しい関数実装を解決し、それをグローバルポインタに格納できます。これらのポインタは書き込み可能ですが、mprotect(2) を使用して初期化後に読み取り専用にすることができます。
  • LD_PRELOAD: 特定のハードウェアターゲットに対しては、LD_PRELOAD 環境変数を使用したラッパー・スクリプトを介して、正しいライブラリ・バージョンを選択できます。
  • 分離されたバイナリ: 異なる CPU 機能セット向けに最適化された複数のバイナリを提供することは、実行可能な戦略です。現代のほとんどの CPU は予測可能な機能ティアに従っています (例: AVX-512 を持つ CPU なら、ほぼ確実に SSE4.2 も持っています)。

パフォーマンスのベンチマークは、IFUNC が関数ポインタの使用よりも大幅に高速であるわけではないことを示唆しています。いくつかのテストでは、IFUNC は単なる関数ポインタ呼び出しよりもむしろオーバーヘッドが発生する場合もあり、その存在の主な正当性を覆しています。

反論とコミュニティの議論

誰もが IFUNC を「犯人」であると同意しているわけではありません。技術コミュニティの一部のメンバーは、IFUNC への注目は、主要な失敗であるサプライチェーンの侵害に目をそらすものだと主張しています。

"攻撃者が共通のライブラリに任意のコードをインストールした時点で、ゲームは決着がついていた... IFUNC、systemd、およびパッチ適用済みの openssh は、すべてこの問題に関係がない。それは単に、この攻撃者が libxz を利用して足がかりを得るために選んだルートであった。"

この視点によれば、もし攻撃者が IFUNC を使用していなければ、別の方法(おそらく C++ のグローバル・コンストラクタや、ディスク上のバイナリへのパッチ適用など)を見つけていたはずです。しかし、反論としては、IFUNC を取り除くことで、ステルス性の高いルートキットの作成を容易にする、非常に強力で非自明なツールを取り除けるという点があります。

結論

CVE-2024-3094 は、ソフトウェアサプライチェーンにおける暗黙的な信頼の危険性を浮き彫りにした「危うい事態」でした。ソーシャルエンジニアリングの側面が主な要因でしたが、GNU IFUNC は、攻撃をエレガントかつ隠密に行うために必要な技術的メカニズムを提供しました。プロセスが完全に保護される前にリンカーが任意のコードを実行することを許可することで、IFUNC は Linux ランタイムの根本的なセキュリティ上の想定を損なっています。エコシステムを強化するためには、コミュニティは IFUNC を glibc の内部インターフェースとして扱い、一般的なアプリケーション・ライブラリにおけるその使用を推奨しない、あるいは無効化することを検討すべきです。

Sources