GHSA-c522-hqhp-v2qq:翻訳ファイル1つで Rust 版 coreutils にコードを注入できる uucore の緊急脆弱性
- Home
- ブログ
勧告: GHSA-c522-hqhp-v2qq
深刻度: 緊急 · CVSS 9.9(v3.1)
ベクター: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
脆弱性の種類: CWE-94(コードインジェクション)、CWE-1395(脆弱なサードパーティ製コンポーネントへの依存)
影響を受けるパッケージ: uucore(crates.io)0.2.2 以上 0.11.0 以下
修正バージョン: 0.12.0
uutils coreutils は、GNU coreutils(ls、cp、cat、install、kill など)を Rust でクロスプラットフォームに書き直したプロジェクトで、現在では主要な Linux ディストリビューションで標準の coreutils として採用されています。これらのユーティリティはすべて、共通の uucore クレートをリンクしています。
uucore のビルドスクリプトが、プロジェクトの翻訳ファイルを安全でない方法で Rust のソースコードに変換していることを発見しました。"# の2文字を含む Fluent(.ftl)翻訳ファイルが1つあるだけで文字列リテラルから抜け出し、ファイルの残りが Rust のコードとしてバイナリにコンパイルされてしまいます。
つまり、レビュアーの誰もが無害なテキストとして扱うファイルが、実際にはコアユーティリティへのサプライチェーン・バックドアの侵入経路になっていたのです。
翻訳ファイルの埋め込みの仕組み
uutils は Project Fluent でメッセージをローカライズしています。バイナリを単体で動作させるため、src/uucore/build.rs はコンパイル時に各 .ftl ファイルを読み込み、生成される Rust ファイル embedded_locales.rs に match の分岐の一つとして書き出します。
// src/uucore/build.rs (vulnerable versions)
// Determine if we need a hash. If content contains ", we need r#""#
let delimiter = if content.contains('"') { "#" } else { "" };
writeln!(
embedded_file,
" \"{locale_key}\" => Some(r{delimiter}\"{content}\"{delimiter}),"
)?;
そして生成されたファイルは、すべてのユーティリティが依存するクレートにそのまま取り込まれます。
// src/uucore/src/lib/mods/locale.rs
include!(concat!(env!("OUT_DIR"), "/embedded_locales.rs"));
embedded_locales.rs に書き込まれた内容は、プログラムと同じ権限でコンパイルされる通常の Rust ソースコードです。
根本原因:raw 文字列は " ではなく "# で終わる
1つのハッシュで開かれた Rust の raw 文字列 r#" にはエスケープシーケンスがありません。最初に現れたバイトの組 "# で、そのまま終了します。
build.rs の判定は、内容にダブルクォートが含まれるかどうかしか確認していませんでした。内容に終端記号そのものが含まれるかどうかは一度も確認していません。そのため、" の直後に # が続く翻訳があればリテラルは途中で閉じ、それ以降はすべて rustc にコードとして解析されます。
概念実証(PoC)
次の内容の翻訳ファイルだけで十分です。Fluent から見れば、これは greeting という名前の単なるメッセージです。
# evil.ftl
greeting = quote "#),
"pwn" => { std::process::Command::new("/bin/sh").arg("-c")
.arg("id > /tmp/UUTILS_FTL_INJECTION_PROOF").status().ok(); None },
"tail" => Some(r#"
build.rs がこれを r#"…"# で囲むと、生成されるソースは次のようになります。
"<locale_key>" => Some(r#"greeting = quote "#),
"pwn" => { std::process::Command::new("/bin/sh").arg("-c")
.arg("id > /tmp/UUTILS_FTL_INJECTION_PROOF").status().ok(); None },
"tail" => Some(r#""#),
文字列は quote の直後で終わり、シェルコマンドを実行する新しい match の分岐が現れ、最後のダミーの分岐が元の閉じ区切り文字を吸収します。ファイルは問題なくコンパイルされ、ペイロードはすべてのユーティリティにリンクされる uucore ライブラリの一部となります。
match の分岐は最も単純な例にすぎません。攻撃者は Rust のトークンをそのまま制御できるため、注入したテキストで関数を閉じ、任意の項目を追加することも可能です。この問題は古いキャッシュではなく、最新の master ブランチで再現を確認しました。
影響:悪意あるプルリクエストによる uutils の侵害
この脆弱性は、たった1つの悪意あるプルリクエストによって uutils そのものを侵害できるものです。攻撃者にコミット権限やメンテナーのアカウントは必要ありません。無害に見える翻訳の変更が1つマージされるだけで十分です。
そのプルリクエストがマージされると、攻撃者のコードは公式リリースと、それを元にビルドされるすべてのディストリビューションのパッケージにコンパイルされます。uutils は、システムスクリプト、インストーラー、パッケージマネージャー、CI パイプラインが常に実行するコアコマンドを提供しているため、これはサプライチェーン全体に及ぶ大惨事となります。つまり、インストールや更新を行うすべてのマシンに、信頼され署名されたシステムバイナリに潜むマルウェアが配布されることになります。
現実的に悪用可能だった理由
- ソースツリーには 214 個の
.ftlファイルがあり、そのうち 34 個はすでに"を含んでいたため、r#"…"#の分岐を通っていました。足りないのは#1文字だけでした。 .github/fluent_linter_config.ymlは Fluent リンターのダブルクォート検査TE04を明示的に無効化していました。翻訳内の引用符はプロジェクトの方針として認められており、CI にはこのパターンを検査する仕組みがありませんでした。- レビュアーは
.rsファイルに注目します。翻訳ファイル内の1行の変更は、特に大規模なプルリクエストの中では、ほとんど注意を払われずに承認されがちです。 - 一度マージされると、ペイロードは次のリリースと、それを元にビルドされるすべてのディストリビューションのパッケージにコンパイルされ、それらのバイナリを実行するすべてのシステムに届きます。そのため、スコアはスコープ変更(S:C)で、機密性・完全性・可用性のすべてに重大な影響があると評価されています。
修正内容
報告では、raw 文字列の使用をやめ、Rust の Debug フォーマッタに正しくエスケープされたリテラルを生成させることを提案しました。メンテナーはこの方法をそのまま採用し、uucore 0.12.0 に含まれています。
// src/uucore/build.rs (fixed)
// Never a raw string: `r#"..."#` ends at `"#`, so content holding that
// pair closes the literal and the rest is compiled as Rust. `{:?}`
// escapes every quote and has no terminator the content can reach.
writeln!(
embedded_file,
" {locale_key:?} => Some({content:?}),"
)?;
{:?} を使うと、すべての引用符・バックスラッシュ・制御文字がエスケープされるため、攻撃用の文字列は通常の文字列リテラル内の無害なデータになります。
推奨される対応
- uutils coreutils / uucore を 0.12.0 以降にアップグレードしてください。
- ディストリビューションのパッケージ担当者やソースからビルドしている方は、修正済みのツリーから再ビルドし、下流やベンダリングされたロケールファイルも確認してください。
- 翻訳ファイルに終端記号の組が含まれていないか監査してください。
grep -rnF '"#' --include='*.ftl' .
この教訓は、build.rs、コードジェネレーター、テンプレートを持つあらゆるプロジェクトに当てはまります。データを文字列に貼り付けてソースコードを組み立ててはいけません。{:?}、quote!、シリアライザーなど本物のエスケープ処理を使い、ビルドスクリプトが読み込むすべてのファイルを、レビューが必要なコードの一部として扱ってください。
最終的な評価
GHSA-c522-hqhp-v2qq は、小さなバグでありながら影響範囲の大きい脆弱性です。ビルドスクリプトでの1文字分の確認漏れにより、リポジトリ内で最も注意を払われない翻訳ファイルが、システム上のほぼすべてのシェルスクリプトを支えるコアユーティリティにコードを注入する手段になっていました。
迅速な修正と協調的な公開を行っていただいた uutils のメンテナーの皆様に感謝します。
参考資料
GitHub セキュリティアドバイザリ
GHSA-c522-hqhp-v2qq
uutils coreutils
https://github.com/uutils/coreutils
Rust リファレンス — raw 文字列リテラル
https://doc.rust-lang.org/reference/tokens.html#raw-string-literals
CWE-94:コード生成の不適切な制御
https://cwe.mitre.org/data/definitions/94.html
発見・報告:JoeCyberTech(GitHub: HackingRepo)
コメントを残す