
DirtyPipe(CVE-2022-0847)に着想を得た、Linuxカーネルにおけるエクスプロイトプリミティブ。
先日、私は多くのセキュリティの先輩方と同じように、DirtyPipe(CVE-2022-0847)の脆弱性を学習し、再現しました。そして、この脆弱性が非常に使いやすいと痛感しました。この脆弱性は、メモリの未初期化の問題に端を発し、任意のファイルの書き換えに至ります。その過程には KASLR の leak や ROP、JOP といった操作は一切含まれません。そのため、SMEP や SMAP などの保護を迂回する必要もありません。
再現が終わった後、私は考え始めました。DirtyPipe というこの脆弱性は、なぜこんなに使いやすいのか? まさか、DirtyPipe が修正されたら、それは儚い一瞬の花のように、将来の脆弱性の悪用に対して何の学習価値も持たなくなってしまうのだろうか?
突然、私は気づきました。DirtyPipe が存在する構造体——struct pipe_buffer——は、非常に聞き覚えがあると。ああ!それはまさに、GFP_KERNEL_ACCOUNT フラグで割り当てられ、slab-1k に位置し、KASLR leak と RIP ハイジャックのためにいつも使用している ops フィールドを含む、あの構造体ではないか!
すぐに自分はバカみたいだと感じました。当時、なぜ pipe_buffer の ops を変更して ROP などしていたのだろう? pipe_buffer の flags を直接変更して splice と組み合わせれば、そのまま任意のファイル書き込みができるではないか!そうすれば kaslr を leak する必要もなく、異なるカーネルバージョンごとに exploit を適合させる必要もなく、gadgets のことを気にする必要もなく、SMEP、SMAP、KPTI などの保護を迂回する必要もありません。
私はすぐに、以前再現・学習した CVE-2021-22555 や、その他 pipe_buffer 構造体に関わる exploit を取り出し、少し変更するだけで、パッチ未適用の異なるバージョンのカーネルで即座に利用に成功しました。以下がいくつかの例です:
これ以前に、これを Linux kernel exploit におけるプリミティブとして利用している人を見たことがなかったため、自分勝手に「Pipe Primitive」と呼ぶことにしました XD。
まとめると、Pipe 構造体を変更する能力を獲得する必要があるということです。たとえば、slab-1k での UAF や、slab-1k でのオフセット付き(つまり順番どおりではない直接の overflow)境界外書き込みなどです。
このように、kernel >= 5.8 では、pipe_buffer 内の splice ページの flag |= PIPE_BUF_FLAG_CAN_MERGE を変更するだけで十分です(余力があれば、ついでに offset と len を 0 に変更すれば、ファイルの先頭から書き込むこともできます)。kernel < 5.8 では、まず pipe_buffer 内の anon_pipe_ops を leak し、それから splice ページの ops を anon_pipe_ops に変更する必要があります(<5.8 では merge できるかどうかは ops によるためです)(余力があれば、やはりついでに offset と len を 0 に変更できます)。
では、これでコンテナエスケープはどうやるのかと聞かれるかもしれませんが、私がやろうとしていることは CVE-2019-5736 とだいたい同じになるでしょう XD。