flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021
CVE-2021-25900:Rust smallvecのヒープバッファオーバーフロー
SmallVec::insert_manyはsize_hintの下限を信頼し、イテレーターが多く返すとヒープバッファを超えて書き込みました。0.6.14と1.6.1で修正。
ロボットがすべての棚を確認してから物を置くので安全だと評判の倉庫があります。いくつかの作業ではロボットを止め、人が手作業で、注意すると約束して作業します。そのうちの一人が運転手に「だいたい何箱持ってきた?」と尋ね、その大きさの棚を組んで積み始めました。見積もりは推測であって保証ではなく、約束より多くの箱が届いたとき、山は棚の端からはみ出しました。
主要な概念と専門用語
SmallVec- 少数の要素を内部に直接持ち、大きくなるとヒープ上のバッファに切り替える Rust のコレクション。その速さは、バッファを手作業で管理する unsafe なコードによるものです。
size_hint- 残りの要素数の推定下限と上限を返す Iterator のメソッド。あくまでヒントで、安全なコードは誤った値を含め、どんな値でも返せます。
unsafe Rust- unsafe ブロック内のコードは、生ポインター経由の書き込みなど、コンパイラーが確認できない処理を行えます。作者はあらゆる安全な呼び出し元に対してメモリ安全性を保つ責任を負います。
健全性(soundness)- どんなに変わった安全なコードでも、その API を通じて未定義動作を引き起こせないなら、安全な API は健全です。insert_many は、正当な size_hint でメモリを壊せたため健全ではありませんでした。
フレームワーク対応
インシデントのタイムライン
Yechan Bae 氏とジョージア工科大学 SSLab の Rust グループがバグを報告する。RustSec が RUSTSEC-2021-0003 を公開し、smallvec 0.6.14 と 1.6.1 で修正が出る。
CVE-2021-25900 が割り当てられ、cargo audit が依存関係ツリー内の影響を受けるバージョンを指摘するようになる。
ステップ・バイ・ステップの攻撃フロー
イテレーターが長さを少なく申告する
size_hint の下限が実際に出す要素数より小さいイテレーターを渡して insert_many を呼び出します。安全なコードにはそれが許されています。
ヒントを基に領域が確保される
insert_many は下限分の領域だけを確保し、既存の要素をその分だけずらします。
それでも全要素が書き込まれる
unsafe ブロックの中でループが生ポインター経由で要素を一つずつ書き込むため、余った要素は確保した領域の末尾の先に書かれます。
ヒープが壊れる
オーバーフローは隣接するヒープのメモリを上書きします。信頼できない入力の影響を受けるイテレーターを insert_many に渡すプログラムが危険にさらされます。
ソースコード比較:脆弱 vs 堅牢化
// *ヒント*の値に基づいて確保する……
let (lower, _) = iter.size_hint();
self.reserve(lower);
unsafe {
let ptr = self.as_mut_ptr().add(index);
for (i, item) in iter.enumerate() {
// ……しかし実際には届いた分だけ
// 項目を書き込む。
ptr::write(ptr.add(i), item);
}
}// 項目ごとに容量を再確認する。
// 安全なコードの値に
// unsafe コードで使う上限を決めさせない。
for (i, item) in iter.enumerate() {
self.reserve(1);
unsafe {
let ptr = self.as_mut_ptr()
.add(index + i);
ptr::write(ptr, item);
}
}
// 実際の修正ではさらに
// unsafe の範囲を大幅に縮小した。エンジニアリング&システム堅牢化チェックリスト
- ✓unsafe なコードが使うバッファの大きさを、size_hint やトレイトオブジェクトの len、比較関数といった安全なトレイトメソッドから決めない。書き込みのたびに容量を確かめ直す。
- ✓すべての unsafe ブロックに、依存する不変条件を書いた // SAFETY: コメントを付け、その不変条件が信頼できない安全なコードに依存しているレビューは通さない。
- ✓size_hint が嘘をつくイテレーター、パニックする Drop、全順序でない比較関数など、敵対的な実装を使うテストを CI の Miri で実行する。
- ✓unsafe ブロックはできるだけ小さく保つ。smallvec の修正でも、検証できるように insert_many の unsafe なコードが減らされた。
- ✓CI で cargo audit(または cargo deny)を実行し、0.6.14 や 1.6.1 より前の smallvec のように RustSec の勧告がある依存関係があればビルドを失敗させる。