Spectre 1.1/1.2の論文は早くから公開されており、Intel / Armも対策のためのWhitePaperを出している。 どいう問題なのか理解するために読んでみた。
- Speculative Buffer Overflows: Attacks and Defenses https://arxiv.org/pdf/1807.03757.pdf
この論文、概念的なところはしっかり説明してあるようなのだが、実際のベンチマーキングなどのところはカバーされていない。 実際にどのような攻撃手法があるのかよく分からなかったので、Intel / Armの文章も参照しながらどういうことなのか勉強することにした。
ただし、筆者自身もセキュリティに詳しいわけではないので理解が足りていない可能性がある。 っていうか、全体的に解説してくれているサイトが少なくて理解するのに時間がかかる...
IntelのWhitepaper (4.0 Bounds check bypass store attacks) https://software.intel.com/sites/default/files/managed/4e/a1/337879-analyzing-potential-bounds-Check-bypass-vulnerabilities.pdf
ArmのWhitePaper (Extending this mechanism to cover the Speculative Execution of Stores, also referred to as ‘Bounds check bypass stores’ (CVE-2018-3693)) https://developer.arm.com/support/arm-security-updates/speculative-processor-vulnerability/download-the-whitepaper
Bounds Check Store Bypass とは
今回のSpectre 1.1の要点は、Spectre 1.0では「投機的なロード」であったものが、「投機的なストア」であるという点が異なる。
ストア命令は命令のコミットが確定するまで実行されない、ただし...
通常、投機的なストアというのは、コミットが確定するまで実行されない。 ストア命令自体を投機してしまうと、意図せずメモリデータを書き換えてしまう可能性があるため、ストア命令自体が本当に実行されることが確定するまでメモリに対して発行はされない。
一方で、高性能なアウトオブオーダプロセッサでは、ストアバッファを用いてストア命令を実行してしまうケースがある。これを"Speculative Store Execution"と呼ぶ。 いったん実行されたストア命令は、投機的にストアバッファに格納される。 ストアバッファに格納されたストア命令のうち、実際に実行した命令のみがストアバッファから取り出され、実行されない命令はストアバッファから破棄されるような方式になっている。
さらに重要なキーワードとして、"Store to Load Forwarding" という技法がある。 例えば、以下のような命令列を考えると、Store命令が格納したアドレスのデータをLoad命令が使用している。
store rA, 100[sp] load rB, 100[sp]
これを何も考えずに実現すると、アドレス依存が存在するためストア命令がメモリにデータを書き込んでからロード命令を実行しないと、Loadは1つ前のStore命令の結果を正しく読み込むことができない。 この問題を改善するために”Store to Load Forwarding"ではLoad命令がストアバッファを探索し、同じアドレスの命令を含んでいればその値を利用するという方式で、ストア命令が実際にメモリに書き込む前にロード命令が値を活用することができるという利点がある。
この手法を逆手に取ったのが、今回のSpectre 1.1の考え方だ。
以下のようなコードを考えたとき、攻撃者はy
を変えて任意の場所にストアを任意の場所に発行させる。
このとき、本来であればlenc
のガードによって変なメモリに書き込まないようになっているが、ガードを超えるようなストア命令でもとりあえずストアバッファに格納される。
void f(u64 x, u64 y, u64 z) { if (y < lenc) c[y] = z; }
ストアした場所(c[y]
)によって、以下の2パターンがある。
- リターンアドレスを指している場合、関数から戻ってくるときに使用される。これはROP(Return Oriented Programming)に悪用される可能性がある。 また、Return Stack Pointerによる関数Returnに失敗するため、新たな値がキャッシュに割り当てられる危険性がある。
- それ以外の値を場合は、任意の場所のデータを読み取ることができるようになる。
Return Stack Pointer とは
Return Stack Pointerは関数からのreturn命令を高速化するため仕組みで、関数の戻りアドレスを専用のスタックに格納しておく。 これは、関数の戻りアドレスが分かっていることが前提となっている。 したがって、関数によってはジャンプ元に戻らないような動作をする場合、このReturn Stack Pointerの内容は使用できず、新しい命令をフェッチしてくる必要がある。
RSP を使ったStore To Load Forwardingによってどのような攻撃ができるのか?
本文中からはあまりよく読み取れなかったが、たぶんReturn Stack Pointerに相当するアドレス(スタックフレームにおいてReturn Addressが配置されるアドレス)を書き換えることによってRSPの値が使用できなくなり、本来の値をキャッシュからロードする必要が生じる。これによるサイクル差により攻撃が可能となる?
また、上記の攻撃でメモリアクセスがスタックフレームを書き換えるようなアドレスだった場合、本来はそれが実行されないのだが投機的実行によりとりあえずストアバッファに入っている場合、Return命令がRSPをミスした影響によりストアバッファを参照し、誤った値をロードする可能性がある。これにより、Spectre 1.0に類似する攻撃が可能になる、ということか?