Spectre/MeltdownのGoogleのプロジェクトゼロブログエントリ に続いて、攻撃を例示する次のコードがあります。
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
}
投機的実行に続いてCPUが実行されるまで、
arr2->data[index2];
状態に達する前
if (untrusted_offset_from_caller < arr1->length) {
それは境界のメモリからのゾーンへのアクセスを防ぎます。
私の質問は:
OSやCPUのメモリアクセスチェックによって停止するはずで、投機的execが単にそれを飛び越えたと思います(?)。
(実際には行われていない)チェック(私の以前の推測が正しいかどうか...)のパッチは十分ではないようですまたは正しいアプローチではありません 、 他の2つの条件が必要であると既に述べられています :分岐予測子をフラッシュし、分岐命令の完全なアドレスを考慮に入れます(現在、CPUはそれを実行していないようです)が、 :
重要な概念は、命令に従ってコードを正確に実行する必要のあるチップはないということです。代わりに、コードは指示に従って正確に実行されたかのように実行する必要があります。最新のプロセッサーはすべて、この自由を利用しています。
理論的には、最終結果が「あたかも」命令がまったく実行されなかったかのように限り、プロセッサはいつでも投機的に任意の命令を実行できます。これは、パフォーマンスを向上させるために使用され、チップのアイドル部分を使用して投機的な作業を実行します。この命令の効果が必要であることを期待しています。
Meltdown/Spectreのバグでは、そのような投機的実行が実際には「あたかも」行われていないことが明らかになりました。この推測により、キャッシュの状態が変更され、他の方法ではキャッシュされない可能性のあるデータが読み込まれました。これにより、Meltdown/Spectreがメモリを読み取るはずのない場所からメモリを読み取るタイミングが変更されます。
重要な失敗は、チップが注文どおりに動作しているかのように動作していないことです。少しずれて行進しています。次のような場合:
if (cursorIdx < cursors.size())
y = buffer[cursors[cursorIdx]];
プログラマーは、バッファーからメモリーを読み取れないことを期待する場合があります。これは、プログラムに書き込まれた命令が、バッファの読み取り前にカーソル配列サイズのチェックを実行することを要求したため、論理的です。チップが「あたかも」命令に従っているかのように動作している限り、サイズチェックの前に発生するバッファの読み取りを誰も観察できないはずであることを証明できます。このエクスプロイトを使用すると、オブザーバーが実際にcanをバッファーから読み取ったことがわかります。
したがって、サイドチャネル攻撃に対しても安全であると思われるコードは、正しく実行されたかのように動作していないため、突然非常に安全ではなくなります。問題は、メモリが投機的に読み取られていることではありません。プロセッサはそれをずっと実行することを許可されており、技術的には許可されていますstill。問題は、このエクスプロイトがそのような投機的読み取りが発生したかどうかを観察できるため、そのような投機的読み取りが決して読み取られなかったかのようではないことを示していることです。彼らは今リーク可能であるはずではなかったメモリ空間についての情報をリークします。
そして、はい、あなたの質問に答えるために、これに対する修正は確かにパフォーマンスに悪影響を及ぼします。このエクスプロイトが非常に重要である理由の1つは、パフォーマンスに影響を与えずにこれらの修正を解決することが非常に難しいことです。
少し単純化すると、問題は
if (*p1) x = p2[256 * *p3];
次のように処理されます:
start loading *p1 into t1.
load *p3 into t2, and set t3 to 0 if it's a valid fetch, 1 otherwise.
load *(p2 + t2*256) into t4
wait for t1..t4 to be ready
if t1 was set, then...
if t3 is set [access was invalid] then fire an invalid access trap.
otherwise copy t4 into x.
discard t1..t4.
*p1
を読み取るとゼロの値が生成される場合、*p3
が無効であることでトラップが発生することはありません(コードが実際に*p3
の読み取りを要求しないため)。何らかの理由で、Intelの設計者は、フェッチされた値を使用して別の投機的読み取りのアドレスを計算するまで、最初のメモリ読み取りの有効性のチェックを遅らせる方が、無効なメモリ読み取りをすぐに投機家に強制させるよりも簡単だと考えましたその予測は間違っていました。
問題は、プロセッサーが* p3から投機的にフェッチすることではないことに注意してください。問題は、プロセッサーが正当に取得されたかどうかに関係なく、その値を使用することです。現在の攻撃はフェッチされた値を使用してアドレスを計算し、次にキャッシュを使用してフェッチされたアドレスを見つけることに重点を置いていますが、根本的な問題は、アクセスが正当かどうかに関係なく、データが読み取られてラッチされることです。デバイスがアクセスできないはずのデータを物理的にフェッチするたびに、サイドチャネル攻撃の可能性が生じます。このような攻撃を防ぐ最善の方法は、最初にそのようなデータをデバイスに取得させないようにすることです。
「通常の実行で、そのメモリゾーンへのアクセスを妨げていたものは何ですか。」何もそれを妨げなかっただろう。しかし、通常、使用されるアドレスは、実行中のコードで合法的に利用可能なデータに基づいていました。そのため、データは投機的に読み取られ、キャッシュラインが排出されます。これにより、読み取られたバイトがわかりますが、とにかく知ることができるバイトです。だから秘密だったものは何も明らかにされなかったでしょう。
これが、サンプルコード(ハードウェア内)によって与えられた状況を処理するための私の提案です。
1回の読み込みで問題ありません。したがって、単純な方法はone投機的読み取り操作のみを許可することです。 2回目の投機的読み取りは、最初の読み取りが投機的でなくなるまで待たなければなりません。
最初の改善:キャッシュを変更しない(または他の方法で情報をリークする)投機的読み取りは問題ありません。したがって、常に1つの投機的読み取りを許可し、キャッシュラインを排出しない(または情報をリークしない)限り、さらに多くの読み取りを許可します。
2番目の改善点:最初の読み取りに依存するアドレスがない限り、それ以降の読み取りは問題ありません。したがって、どのレジスターが投機的読み取りの結果であるかを追跡し、これがどのように伝播するかを追跡し、アドレスが投機的読み取りに基づいていない限り、さらに読み取りを許可します。これにより、「if(x> 0)z = a [0] + a [1] + a [2];」が許可されます。続行します。
3番目の改善:別のプロセスに属するデータを読み取ろうとするとキャッシュミスが発生するように、L1キャッシュを変更します。これで、読み取り操作がL1キャッシュにヒットすると、データの読み取りが許可されたことがわかりました。したがって、L1キャッシュヒットであったすべての読み取りを無視します。
私はcirculosmeosが何を求めているのかを理解しています。この質問も私の頭に浮かんでいます。権限のないメモリの読み取りが通常の実行で許可されていない場合、なぜそれが投機モードで発生するのですか?
論理的には、それも投機に失敗し、投機的ブランチを中止させるなどの期待があります。確かにアクセスエラーは登録されていますが、コードが「本物」になった場合にのみ適用されます。
私の学んだ推測では、デザインをよりシンプルで高速に保つことが選択でした:投機的ブランチのすべての効果が破棄され、残りのコードから見えない場合、なぜ必要以上にロジックを複雑にする必要があるのでしょうか。そのような獣を設計するとき、すべてのトランジスタとすべてのナノ秒が重要です。
問題は、キャッシュが原因で、仮定が正確ではなかったことです。すべての効果が元に戻るわけではなく、エンジニアはこの事実に気付かなかった、またはサイドチャネル攻撃がそのような効果を検出できることを知らなかった、またはそれらの効果を認識しなかった手の込んだ方法で使用すると、実際にメモリの内容を明らかにすることができます。
非常に役立つすべての返信を注意深く調査し、収集したすべての情報を注文するために、満足のいく回答として役立った、私がここで得た知識の素早い再開を行います。
まず、@ cort-ammonのコメントにとても感謝しています。
"重要な概念は、チップが命令に従ってコードを正確に実行する義務を負わないことです。代わりに、コードが命令に従って正確に実行されたかのように実行する必要があるという義務があります。すべての最新のプロセッサが利用されますこの自由の。」
この概念は、これらのすべてのCPU機能(分岐ターゲット予測、投機的実行など)が最初から実装されている理由を理解するために本当に必要です。いつものように。
今質問については:
Nothing(@immibis&@ gnasher729が指摘したとおり!):このバージョンのSpectreの脆弱性は、プロセッサがプロセスにアクセスできると判断したデータの読み取りのみを許可するためです。
たとえば、同じプロセスメモリ空間内のバッファオーバーフローからの読み取りとコードに違いはありません( このコードの例を参照 )。
つまり、質問に示されているコードはバリアント1:境界チェックバイパスから抽出されます。ここで、一般的な概念実証(PoC)が表示されます。このPoCはさまざまな方法で使用されます。カーネルを攻撃するためにVariant 1のコードを使用するには、eBPFバイトコードインタープリターへのアクセスが必要であり、"非特権ユーザースペースコードはカーネルにバイトコードを提供できます"。これは、eBPF( " extended Berkeley Packet Filter "、Linuxカーネルのプログラム可能な機能)の特徴的な(!)動作であり、攻撃が最大限に利用します。このようにして、攻撃はカーネルに侵入します。アラームをトリガーすることなく、禁止されているはずのメモリ位置を読み取ることができます(想定された予約メモリの範囲外ですが、同じプロセス空間にあります)。また、eBPFコンパイラによってコードが調査されたとしても、 (そうではありません)、範囲外の直接のメモリ読み取りはまったく行われないためです。
しかし、PoCはバリアント2:ブランチターゲットインジェクションでも使用できます。この場合、他のトリックを使用して、CPUに誤った推測の実行でダイレクトジャンプを強制します他のプロセスの任意の仮想メモリ位置への読み取りアクセスを得るため。この攻撃は、一部のCPU(少なくともIntel Haswell Xeon)のBranch Target Buffer(BTB)が完全なメモリアドレスの一部のみを使用して格納するという事実を最大限に活用します分岐予測情報。これは、あるプロセスが別の完全に異なるプロセスの分岐ターゲット予測子に影響を与え、ユーザースペース/カーネル(および他の)保護をバイパスする能力(攻撃)につながる事実の一部です。
そして今、私はこの部分的なメモリアドレスBTBがどこに適合するかを理解しています 私が指摘した答えで 私の質問では、CPUをSpectre攻撃から保護するために次のように述べています:
"分岐予測子の状態は、分岐命令の完全なアドレスを考慮する必要があります(現在、スペースを節約するために、下位ビットのみが使用されます)]"( @ Mark )
2番目の質問については:
これらの攻撃の内部と、CPU最適化(BTB、投機的exec ...)の内部も認識しているので、質問はそれほど必要ではないと思われます。CPU設計者は、パフォーマンスとセキュリティのバランスを維持しようとします。 ...これらの攻撃が公表される前は、セキュリティがこのバランスになかっただけかもしれません(非常に有望なサイドチャネル攻撃はすでに知られていましたが...それは別の話です)。
たとえば、(再度)@ cort-ammonポイントとして:
"たとえば、投機的な読み取りが落ちた場合に次世代CPUがキャッシュラインをエビクトさせずに、「まるで」のように一歩近づけるかどうかをIntelデザイナーが今調べていることは確かです。
すべての回答ありがとうございます。