<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Spectre</title><link>https://blog.ast.moe/tags/spectre/</link><description>最近の記事: 五里霧中</description><generator>Hugo</generator><item><title>今更Spectreをちゃんと理解したら、SharedArrayBufferが思っていた方向と違うまずさだった</title><link>https://blog.ast.moe/blog/2026-10-16/</link><pubDate>Fri, 16 Oct 2026 21:37:01 +0900</pubDate><guid>https://blog.ast.moe/blog/2026-10-16/</guid><description>&lt;p&gt;SharedArrayBufferがブラウザでデフォルトでは使えないのは、Spectre対策のためだということは知っていた。ただ、Spectreについては「メモリ周りの脆弱性」という漠然とした理解で止まっていた。&lt;/p&gt;
&lt;p&gt;SharedArrayBufferも共有メモリを扱う低レイヤーなAPIだし、これを使うとメモリ周りで何か悪さができるのだろう。そのくらいで納得していて、Spectreの仕組みや、このAPIが攻撃にどう使われるのかまでは説明できなかった。&lt;/p&gt;
&lt;p&gt;今更調べてみたら、思っていた方向と違うまずさだった。SharedArrayBufferから他人のメモリを直接読めるという話ではなく、&lt;strong&gt;共有メモリで精密な時計を作れる&lt;/strong&gt;という。&lt;/p&gt;
&lt;p&gt;でも、正確に時間を計測できると、何がダメなんだろう。処理にかかった時間が分かるなら便利そうだし、それだけで読めないはずのメモリが読めるようになるのか。この間が、自分にはつながっていなかった。&lt;/p&gt;
&lt;p&gt;そこで分かってきたのが、CPUが先走って実行した処理の痕跡を、時間差から測れるという話だった。秘密の値が戻り値として出てこなくても、その値によって「どこを読むと速いか」が変われば、時計を使って推測できる。メモリのAPIの話を読んでいたはずなのに、時計の話になった。&lt;/p&gt;
&lt;p&gt;そこから先も長かった。時計を粗くするだけでは足りず、秘密のデータを攻撃者と同じプロセスに入れない話になり、COOPとCOEPというHTTPヘッダーに行き着く。さらに、なぜウィンドウ間の関係を切る必要があるのかを追うと、browsing context groupやWindowProxyまで出てくる。&lt;/p&gt;
&lt;p&gt;ここでは、その順番で理解したことを書いておく。攻撃を自分のマシンで再現した記録ではなく、論文、ブラウザ実装側の説明、Webの仕様を読んで整理したものだ。コードや図は、明記したものを除いて説明用で、攻撃の実行例や計測結果ではない。&lt;/p&gt;
&lt;p&gt;長くなったので、途中から読むなら次のあたりから。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.ast.moe/blog/2026-10-16/#speculation"&gt;投機実行とキャッシュの痕跡&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ast.moe/blog/2026-10-16/#shared-memory-clock"&gt;SharedArrayBufferで作る時計&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ast.moe/blog/2026-10-16/#browser-isolation"&gt;ブラウザにあるデータとプロセスの隔離&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ast.moe/blog/2026-10-16/#coop-coep"&gt;COOPとCOEPがそれぞれ制限するもの&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ast.moe/blog/2026-10-16/#windowproxy-realm"&gt;WindowProxy、メッセージの同一性、Realm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="最初に何が意外だったのか"&gt;最初に、何が意外だったのか&lt;/h2&gt;
&lt;p&gt;調べる前は、何となくこう思っていた。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Spectreは、メモリ周りの脆弱性らしい
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → SharedArrayBufferは、共有メモリを扱う低レイヤーなAPI
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → メモリ周りの悪さを何か手助けするのだろう
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → だからSpectre対策でデフォルトでは使えない
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;しかし、今回理解したつながりはこうだった。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;投機実行中に秘密の値が使われる
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → その値に応じたCPUキャッシュの痕跡が残る
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → メモリへのアクセス時間を測ると秘密を推測できる
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → 共有メモリで作った時計が、その測定に使える
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; → 時計の制限と、秘密のデータの隔離が必要になる
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SharedArrayBufferはこの流れの途中にある。秘密を置く場所や、秘密を直接読むためのAPIとして登場するとは限らない。攻撃者が自分のコード同士で共有するメモリでも、測定器として役に立ってしまう。&lt;/p&gt;
&lt;p&gt;なお、デフォルトでは使えないことと、全面的に禁止されていることは別だ。一時的な無効化を経て、現在の通常のWebページではcross-origin isolationなどの条件を満たせば使える。&lt;a href="https://developer.chrome.com/blog/enabling-shared-array-buffer/"&gt;Chrome側が説明する無効化と再導入の経緯&lt;/a&gt;に、この時計の問題が書かれている。&lt;/p&gt;
&lt;p&gt;&lt;a id="speculation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="cpuは条件の結果を待たずに先へ進む"&gt;CPUは、条件の結果を待たずに先へ進む&lt;/h2&gt;
&lt;p&gt;まず投機実行から考える。&lt;/p&gt;
&lt;p&gt;プログラムには、条件が確定するまで次に進めないように見える箇所がある。例えば、配列の添字が範囲内か調べてから、その要素を読む処理だ。ソースコードを上から読めば、チェックが先で、読み取りがあとになる。&lt;/p&gt;
&lt;p&gt;一方、CPUが本当にその順で何もかも待つと、メモリから値が届くまで使われない実行資源が生まれる。そこで、条件がどちらに進みそうかを予測し、正しいか確定する前に先の命令を実行する。予測が当たれば、その分だけ待ち時間を隠せる。&lt;/p&gt;
&lt;p&gt;外れた場合は、誤った経路で実行した命令の結果を、プログラムの確定した結果としては採用しない。&lt;a href="https://projectzero.google/2018/01/reading-privileged-memory-with-side.html"&gt;Project Zeroの最初の報告&lt;/a&gt;では、この「実行」と、結果を確定するretireを区別して説明している。&lt;/p&gt;
&lt;p&gt;ここで自分が引っかかったのは、「実行したのに、実行していないことにできるのか」という点だった。プログラムに見える結果については、そういう仕組みで動いている。誤予測した経路で計算した値を、そのまま正しい戻り値として返すわけではない。&lt;/p&gt;
&lt;p&gt;ただし、「プログラムが正しい答えを返す」ことと、「途中で何をしたかを一切観測できない」ことは別だった。&lt;/p&gt;
&lt;h3 id="確定した状態と速くするための状態"&gt;確定した状態と、速くするための状態&lt;/h3&gt;
&lt;p&gt;この区別には、architectural stateとmicroarchitectural stateという言葉が出てくる。&lt;/p&gt;
&lt;p&gt;前者は、命令セットの約束としてプログラムから見えるレジスタやメモリなどの状態。後者は、その命令セットを実際に速く動かすためにCPU内部で持つ状態で、キャッシュや分岐予測器などが関わる。&lt;/p&gt;
&lt;p&gt;例えば計算を破棄して、レジスタに誤った答えを残さないようにしても、その計算の途中で読み込んだデータがキャッシュに載ることまでは、同じように取り消されるとは限らない。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://spectreattack.com/spectre.pdf"&gt;原論文&lt;/a&gt;で重要なのは、この差を情報の出口として使ったことだ。秘密を戻り値に載せなくても、秘密によって変わった内部状態を、あとから観測できればよい。&lt;/p&gt;
&lt;p&gt;「実行を取り消す」という説明を、自分は「時間も含めて完全に元通りにする」と受け取っていた。そこが違った。&lt;/p&gt;
&lt;h2 id="秘密を返さず秘密に応じた場所へ触る"&gt;秘密を返さず、秘密に応じた場所へ触る&lt;/h2&gt;
&lt;p&gt;キャッシュは、メモリアクセスを速くするためにデータを近くへ置く仕組みだ。同じデータを読む場合でも、キャッシュにあるかどうかで、かかる時間が変わる。&lt;/p&gt;
&lt;p&gt;ここで、秘密の1バイトが42だったとする。攻撃者は42そのものを通常の読み取り結果として受け取れない。しかし、投機実行の間だけその値を使い、観測用の領域の「42に対応する場所」を読む処理を走らせられたらどうなるか。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;秘密が 7 → 観測用領域の 7 に対応する場所へアクセス
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;秘密が 42 → 観測用領域の 42 に対応する場所へアクセス
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;秘密が 99 → 観測用領域の 99 に対応する場所へアクセス
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;あとから攻撃者は、観測用領域の候補を読む。そのうち42に対応する場所だけが速ければ、秘密は42だったのではないかと推測できる。&lt;/p&gt;</description></item></channel></rss>