脆弱性を探しに行って見つけた話

に公開

はじめに

no starch pressからFrom Day Zero to Zero Day[1]という本が出たので読んでいました。
第3章まで読んだので一旦実践してみようと思い自分で未知の脆弱性を探していたところ、運良く一個見つけたのでそこに至るまでの過程を書いていきたいと思います。

ターゲットを探す

本には

  • Familiarity
  • Availability
  • Impact

を意識してターゲットを探すとよいというようなことが書いてあったので、まず自分の得意なRustで作られているOSSから脆弱性を探すことにしました。

既知の脆弱性を探す

3章 Variant analysis に既知の脆弱性から直し忘れを探すと良いということが書いてあったのでRUSTSECとかGitHub Advisory Databaseを見ることにしました。
脆弱性を探す目的でOSSを探すことは今までなかったのですが、少なくともそこからなにかインスピレーションを得られないかと思いながら見ていました。

そこでピンときたのが、bytecodealliance/wasmtimeWasmtime doesn't fully sandbox all the Windows device filenamesでした。

この脆弱性の詳しい内容は割とどうでもいいのですが、Webassembly(WASI)のファイルシステムのサンドボックスという部分に何か直感を感じました。
かなりわかりやすくSource-Sink analysisでいうSinkです。
自分で実装することを想像したらちょっと嫌だし、絶対他に何かバグがあるだろうと思いました。

本に載ってたような、CodeQLとかSemgrepなどの静的解析を使ったVariant analysisは出来なさそうですがとりあえずコードを見てみることにしました。

コードレビュー

WasmtimeWASIの実装は

といくつかありますが、運良く最初に開いたPreview1の実装でなにやら怪しい部分を見つけました。

ここです
https://github.com/bytecodealliance/wasmtime/blob/804060c8ea7a1f938f896c3af3a65ed44b115778/crates/wasi/src/preview1.rs#L286-L304

このimpl DerefMut for Descriptors非常に強い違和感を覚えました。
DescriptorsはLinuxのfile descriptorと同じでファイルとかを開いたときとかに割り振られるやつです。
問題はこのDescriptorsは常識的に考えて、

\mathit{keys}(\mathit{used}) \cap \mathit{free} = \varnothing

の関係を維持する必要がありますが(usedfreeが同じ数字のdescriptorを持っていたらなにか変なことが起きるということが言いたい)
impl DerefMut for Descriptorsがあることによって、別の場所でusedのみを触る可能性がある → 間違って上記の関係がぶっ壊れる可能性があります。

というわけでDescriptorsDerefMutを使っているところを探したところ見つかったのがこれです。

https://github.com/bytecodealliance/wasmtime/blob/804060c8ea7a1f938f896c3af3a65ed44b115778/crates/wasi/src/preview1.rs#L1825-L1836

fd_renumberはWASI preview1のdup2(2)のようなものです。

st.descriptors.insert(to.into(), desc);

この部分がfreeをケアしていなくて、明らかにヤバそうです。

というわけでPOC

// wasi = "=0.11.1"
use std::os::fd::{AsFd, AsRawFd};

fn main() {
    let file0 = std::fs::File::create("test0").unwrap();
    let fd0 = file0.as_fd().as_raw_fd();

    unsafe { wasi::fd_renumber(fd0 as u32, fd0 as u32) }.unwrap();

    let file1 = std::fs::File::create("test1").unwrap(); // This line cause a panic in assertion
    let fd1 = file1.as_fd().as_raw_fd();

    dbg!(fd0, fd1);
}

これが実行時になんやかんやで
https://github.com/bytecodealliance/wasmtime/blob/968952abe5f317313032a5442a301b3fdd198d56/crates/wasi/src/preview1.rs#L415
でpanicします。

panicは脆弱性なのか

結論から言えばプロジェクトによります。

wasmtime他の脆弱性を見てみると、panicはDoS扱いになるようです。
しかし、他のRustプロジェクトのcloudflare/pingoraを見てみるとただのpanicくらいなら(単一のコネクションがおかしくなるだけなので)脆弱性とまではいかない感じがします。

レポートを送る

wasmtimeではこのバグは脆弱性な気がしたためGitHubのSecurityタブからレポートを送りました。

結果

すぐに返信があり、約一週間でパッチがリリースされ公開となりました。
CVE-2025-53901としてCVEの番号もつきました。🥳

ページにはしっかりと脆弱性の詳細が書かれていますが、これは後でプロジェクトの方が作ってくれた文章です。
なのでレポートする時点でここまでしっかり書く必要はなく開発者の方に意図が伝われば十分な気がします。

おわりに

  • 意外に探せば脆弱性ある
  • コード全体を見なくても局所的な情報からなにか見つけられる
脚注
  1. 正式な発売日は2025/8/12のようですが、O'Reilly Safari Books Onlineではすでに読めます。おすすめ ↩︎

GitHubで編集を提案

Discussion