Rustが捕まえられないバグ
概要
- 2026年4月、Canonicalがuutils(Rust製GNU coreutils再実装)の44件のCVEを公開
- 多くは26.04 LTSリリース前の外部監査で発見
- Rustの安全機構(borrow checker、clippy、cargo audit)をすり抜けたバグが多数
- 監査結果はRustの安全性の限界を示す貴重な教材
- 本内容はuutilsチームへの批判ではなく、学びの共有
Rustで発生した44件のCVEから学ぶシステムプログラミングの教訓
- uutilsはRustで書かれたGNU coreutilsの再実装であり、Ubuntu 25.10以降デフォルトで採用
- 2026年4月、Canonicalが44件のCVEを公開
- 多くは外部監査による発見、26.04 LTSリリースの事前準備として実施
- Rustの型安全性や静的解析ツールでは検出できなかった実バグ
- uutilsチームは詳細な監査結果を公開し、コミュニティ全体の学びに貢献
システムコール間のパスの信頼性(TOCTOU問題)
-
TOCTOU(Time Of Check To Time Of Use)バグが最大のクラスター
-
例:fs::remove_fileでファイル削除後、File::createで再作成する間にシンボリックリンクへすり替え可能
-
攻撃者が親ディレクトリへの書き込み権限を持つと、特権操作が任意ファイルに作用
-
Rust標準ライブラリのfs::metadata, File::create, fs::remove_file, fs::set_permissionsなどのAPIはパスベースで再解決
-
**OpenOptions::create_new(true)**を使い、ファイル新規作成時のみ安全性を担保
-
それ以外の場合は親ディレクトリのファイルディスクリプタを基準に相対パスで操作
- ルール:同じパスに2回作用するならTOCTOUを疑い、ファイルディスクリプタ基準で処理
パーミッションは作成時に設定
-
ディレクトリやファイルの作成後にset_permissionsでパーミッションを修正すると、短時間デフォルト権限で存在
-
他ユーザーがその間にopen()可能、chmodしても既存のfdは権限維持
-
**DirBuilderExt::mode()やOpenOptions::mode()**で作成時にパーミッション指定
-
umaskの明示的設定も重要
- ルール:パーミッションは必ず作成時に指定、後から修正しない
パスの文字列比較はファイルシステム上の同一性を保証しない
-
例:--preserve-rootの判定が"/"と文字列比較のみだと"/../"やシンボリックリンクで回避可能
-
fs::canonicalizeで正規化し絶対パス比較
-
より厳密には**(dev, inode)**ペアで比較
- ルール:パス比較は文字列ではなく、正規化またはファイルシステムIDで行う
Unix境界ではバイト列で扱う
-
RustのStringや**&str**は常にUTF-8、しかしUnixのパスや環境変数、標準入出力はバイト列
-
from_utf8_lossyは不正バイトをU+FFFDに変換しデータ破壊、unwrapや?はクラッシュ
-
OsStr/OsStringや**&[u8]**、**Vec<u8>**でバイト列として扱うべき
-
print!はUTF-8経由だが、write_allはバイト列をそのまま出力
- ルール:Unix系の生データはバイト列型で処理し、String経由の変換は避ける
panic!はDoS攻撃の温床
-
unwrap、expect、インデックスアクセス、unchecked演算、from_utf8などはpanic!でプロセス全体が異常終了
-
CLIやバッチ処理、CI環境ではDoS(サービス不能)につながる
-
例:sort --files0-fromで非UTF-8ファイル名にexpectを使い即panic
-
Clippyのunwrap_used、expect_used、panic、indexing_slicing、arithmetic_side_effectsをwarn指定
-
テストコードではpanic許容、CIでは本番コードのみ警告
- ルール:不正入力はpanic!ではなくエラーとして返却、unwrap/expect禁止
エラーは捨てずに伝播
-
chmod -Rやchown -Rが最後のファイルのエラーコードのみ返却、途中失敗が無視される
-
ddがset_lenのResultを.ok()で黙殺し、ディスクフルでもエラー無視
-
let _ =や.ok()でResultを捨てる際は、なぜ安全かコメント必須
- ルール:意味あるエラーは必ず伝播し、最悪ケースを記録
オリジナルツールとの完全互換性
-
多くのCVEは「GNU coreutilsと挙動が違う」ことが原因
-
例:kill -1の解釈違いで全プロセスkill
-
オプション、エラーコード、エラーメッセージ、端的な挙動までバグ互換が安全性
- ルール:バトルテスト済みツールの再実装では、バグ含めて挙動を完全再現
まとめ
- Rustは強力な安全機構を持つが、TOCTOU問題やパーミッションのタイミング、バイト列処理、panic!によるDoSなど、設計・実装レベルでの注意が不可欠
- 監査レポートはRustによるシステムプログラミングの落とし穴とベストプラクティスの宝庫
- uutilsチームの透明性とコミュニティ貢献に感謝