SharedArrayBuffer と過渡期な cross-origin isolation の話
by えーじ / Eiji Kitamura
2021/12/26: Safari も 15.2 から COOP/COEP を使って SharedArrayBuffer
が利用できるようになったので、該当箇所の表記を変更しました。
長い記事なので先に結論を書きます。
Chrome、Firefox および Safari で SharedArrayBuffer
や高精細タイマーが使えるようになりました。そのためには cross-origin isolation という状態を有効にするのですが、親となる HTML ドキュメントに下記 2 つのヘッダーを送ります。
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
ただ、これを有効にするには様々な条件と制約が存在し、現段階では多くのサイトは苦戦するでしょう。とりあえず従来通り Chrome で動けばいいやということであれば、デプリケーショントライアル (Deprecation Trial) に登録してしばらく様子を見る、という選択肢が無難かもしれません。
Spectre の脅威とブラウザの対策、そして Site Isolation #
前回の記事では、Spectre の脅威として、同じプロセスの扱うメモリ空間を推察することで cross-origin に読み込まれたリソースが危険に晒されることを説明しました。各ブラウザはその対策として SharedArrayBuffer
を無効にしたり、高精細タイマーの精度を下げるなどしてリスクを緩和したこと、一部のブラウザは Site Isolation というアーキテクチャを導入することで抜本的な対策を実現したこと、標準化された機能を使うことで cross-origin なページによる攻撃からリソースを分離して、安全性を確保できることを書きました。具体的には、CORP, X-Content-Type-Options
, X-Frame-Options
, CSP frame-ancestors
, COOP といった各種 HTTP ヘッダーを用いることで、レンダラプロセスに渡る前にリソースを守ります。
Site Isolation を採用したブラウザは再び SharedArrayBuffer
や高精細タイマーが利用できるようになりましたが、例えすべてのブラウザが Site Isolation に対応したとして、アーキテクチャに依存してそれらの機能が使えたり使えなかったりするのは、ウェブにとって健全なことと言えるでしょうか?
そこで登場するのが cross-origin isolation です。これは、いくつかの HTTP ヘッダーを組み合わせることで、ブラウザが他の origin から完全に切り離された安全な環境である (cross-origin isolated) と判断した状態のことで、SharedArrayBuffer
や高精細タイマーなどを有効にします。
この記事では、そんな cross-origin isolation を有効にする方法とその課題、次のステップについて解説します。
Cross-origin isolated な環境では何が可能なのか #
Cross-origin isolation を有効にすると、下記のことが可能になります:
SharedArrayBuffer
が使えるようになる (Wasm Thread が使えるようになる)performance.measureUserAgentSpecificMemory()
が使えるようになるperformance.now()
およびperformance.timeOrigin
の精度が上がる
Chrome ではしばらくの間 Site Isolation を導入することで SharedArrayBuffer
や高精細タイマーを使えるようにしていましたが、Chrome 92 よりその前提を外し (他のブラウザと同じ条件に変更し)、cross-origin isolated な状態であることを条件とするように変更されました(その節はたいへんお騒がせしました)。
標準化された技術を使って cross-origin isolation を有効にする #
ウェブページが他の origin から完全に切り離された安全な環境である cross-origin isolation を有効にするには、現時点で 2 つの条件があります。
条件 1. HTML ドキュメントが Cross-Origin-Opener-Policy: same-origin
ヘッダーを送っている #
ブラウザは window.open()
で新しいウィンドウを開く際、postMessage()
などを使ったコミュニケーションを維持するため、cross-origin であっても同じプロセスを使います。前回の記事では COOP ヘッダーを使うことでプロセスを分け、Spectre の脅威を回避できる手段をご紹介しました。COOP: same-origin
と設定した場合、開かれたウィンドウが same-origin でない限り別プロセスになるため、安全性を担保できます。
Cross-Origin-Opener-Policy: same-origin
ただし、これは相互に postMessage()
を使ったコミュニケーションができなくなる点に注意しなければなりません。
これが cross-origin isolation を有効にするための条件 1 です。
条件 2. HTML ドキュメントが Cross-Origin-Embedder-Policy: require-corp
ヘッダーを送っている #
前回の記事では出てこなかったヘッダー Cross-Origin-Embedder-Policy
(COEP) は、それ自体安全性のために用意されたものではありません。COEP は許可されていないリソースの埋め込みをすべて排除することで危険に晒されるリソースをなくし、cross-origin isolation を実現するためのものです。COEP: require-corp
を指定すると、明示的にこのページに読み込まれることを CORS または CORP で許可しているリソース以外は、すべてブロックされます。
Cross-Origin-Embedder-Policy: require-corp
これが cross-origin isolation を有効にするための条件 2 です。
cross-origin isolated かどうかを確認する #
上記 2 つのヘッダーを送っている状態のウェブページが cross-origin isolated な状態になっているかどうかは self.crossOriginIsolated
で確認することができます。true
を返せば cross-origin isolated、false
を返せば否です。
if (self.crossOriginIsolated) {
// The environment is cross-origin isolated.
} else {
// The environment is NOT cross-origin isolated.
}
Cross-origin isolation はこちらのデモから試すことができます。
リソースがブロックされてしまう!? #
ここで終われば話は非常に簡単なのですが、難しいのはここからです。実際に cross-origin isolation を試してみた方はお気付きと思いますが、これだけでは普通のウェブサイトは完全にぶっ壊れます。なぜなら、特別な手当てをしていない cross-origin なリソースはすべてブロックされてしまうからです。cross-origin または same-site かつ cross-origin なリソースを読み込むには、明示的に CORS もしくは CORP を設定して、cross-origin からロードされても問題ないことを示す必要があります。
リソースに CORS もしくは Cross-Origin-Resource-Policy
ヘッダーを付与する #
ここで言う「リソース」はドキュメントや画像、動画、フォント、スクリプト、スタイルなど、HTML ドキュメントから読み込み可能なものすべてを指します。
前回の記事でご紹介したように、CORP はリソースが、same-origin
なら same-origin からのみ、same-site
なら same-site からのみ、cross-origin
ならどんな origin からであっても、リソースがロードが可能であることを示します。
例えば https://www.example.com
が COEP: require-corp
を送っている場合、ある画像がロードされる条件は CORS に対応している、もしくは:
- 同じ origin から配信されている場合は無条件にロードされる (
CORP: same-origin
が指定されていてもよい)。 - 同じ site (例:
https://images.example.com/image.png
) から配信されている場合はCORP: same-site
もしくはCORP: cross-origin
があればロードされる。それ以外はブロック。 - 全く別の site から配信されている場合は、
CORP: cross-origin
であればロードされる。それ以外はブロック。
Cross-Origin-Resource-Policy: cross-origin
CORS を使う場合は、リソースの読み込みでそれを要求する必要があります。具体的には、例えば <img>
タグに crossorigin
属性を付けることで、CORS リクエストを送ることができます。
<img src="***/image.png" crossorigin>
crossorigin
属性は <audio>
, <img>
, <link>
, <script>
, <video>
タグに追加することができます
COEP と CORS/CORP を組み合わせた動作はこちらのデモから試すことができます。
iframe に読み込む HTML ドキュメントには COEP も追加する #
iframe に読み込まれる HTML ドキュメントも cross-origin であれば Spectre の脅威に晒されるということは前回の記事でも書きました。では、その cross-origin な HTML ドキュメントがさらに cross-origin なリソースやドキュメントを読み込んでいる場合どうなるのでしょう?
実は、再帰的に要件を満たさなければ、すべてブロックされます。iframe を埋め込むためには、それ自体にも COEP: require-corp
が必要になります。
まとめると、cross-origin isolated なページに cross-origin な HTML ドキュメントを iframe で埋め込む場合は、その iframe にロードされる HTML ドキュメントも:
COEP: require-corp
であることCORP: cross-origin
であること (same-site / cross-origin ならCORP: same-site
でも可)
となります。
-
この時 iframe 内の
self.crossOriginIsolated
はfalse
になりますが、iframe タグにallow="cross-origin-isolated"
を指定することでtrue
にして、SharedArrayBuffer
などを利用することができるようになります。 -
iframe 内だけ cross-origin isolation を有効にしたいというケースもあるかもしれませんが、残念ながらそれをする方法はありません。同じページ内に存在するすべてのフレームが一番親であるフレームの cross-origin isolation の一部になっている必要があります。
iframe についても、こちらのデモから試すことができます。
ここまでの内容はすでに Chrome, Edge, Firefox, Safari の、すべてのメジャーブラウザが対応しています。。
Cross-origin isolation の課題とその対応策 #
上記したことを完全に実行すれば、ブラウザ上で SharedArrayBuffer
などが利用できるようになります。
ただ、まだ課題は残ります。
- 課題 1.
COOP: same-origin
は OAuth や支払いなどの popup ウィンドウを使う連携を壊す。COOP: same-origin
の性質上、cross-origin なウィンドウを開いて通信を行う OAuth や支払い系によくある連携はできなくなってしまいます。 - 課題 2. CORS や
CORP: cross-origin
を指定しようにも、他社のリソースなので指定できない。
これも cross-origin isolation の典型的な問題です。
例えば Google から配信されているリソースの多くはすでに CORP: cross-origin
に対応済みですが、上記のような課題から、cross-origin isolation に対応していないサービスも存在しています。例えば Google Ads は iframe を使って広告を配信していますが、iframe の中身を広告主が配信しているケースもあり、そのすべてに CORS や CORP の導入を求めるのは現実的ではないため、対応しない意向を示しています。
これらを踏まえ、Chrome では cross-origin isolation なしで SharedArrayBuffer
を再び有効にする方法と、標準仕様の側から cross-origin isolation を有効にする条件を緩和しようという議論が進んでいます。
Chrome で cross-origin isolation なしで SharedArrayBuffer
を有効にする #
Chrome は元々 Site Isolation というアーキテクチャに対応しており、cross-origin isolation に移行したのは他のブラウザと足並みを揃えるためである、と説明しました。ただ、上記のような問題から、cross-origin isolation に対応しないでも SharedArrayBuffer
を継続して利用する選択肢も用意されています。デプリケーショントライアル (Deprecation Trial) という仕組みを適用することで、少なくともこの後述べる改善策が準備されるまでの間は、引き続き SharedArrayBuffer
を従来通り利用することができます。
参考: SharedArrayBuffer updates in Android Chrome 88 and Desktop Chrome 92
2021 年 11 月現在、Chrome 103 までこのデプリケーショントライアルで回避できると書いてありますが、下記の改善策の導入が間に合わなければ、延長される可能性があります。延長されるかどうかは Origin Trial に申し込んでいればメールでお知らせされると思いますが、(このブログ記事が更新されないとしても) 上記のブログポストを更新します。
デプリケーショントライアルに登録するには、こちらから origin を指定して申し込み、発行されたトークンをサイトの Origin-Trial
ヘッダーもしくは <meta>
タグで配信します。詳しくは、ちょうど日本語に翻訳済みの Chrome のオリジントライアル入門をご覧ください。
Cross-origin isolation の条件を緩和する #
標準化の面からも、cross-origin isolation をより柔軟にする取り組みが行われています。そのために提案されている仕様を紹介します。
COEP: credentialless
#
他サービスの提供するリソースに CORS や CORP の対応を求めるのは難しいというのが課題ですが、そもそもそれは必要なことでしょうか?リソースの多くは画像やスタイル、フォントなどのリソースであり、インターネットに公開されたものです。それらは URL さえ分かれば誰でもダウンロードできるはずで、守るのであれば認証を挟むべきです。
それならば、COEP のモードとして CORS や CORP を必須とするのではなく、認証しないでリクエストする前提のモードを作ってしまえばいいのではないか、ということで考え出されたのが COEP: credentialless
です。
Cross-Origin-Embedder-Policy: credentialless
COEP: credentialless
を使うと、サーバーへのリクエストから Cookie、クライアント認証、Authorization ヘッダーといった認証方法が省かれます。これにより、サードパーティーリソースを危険に晒すことなく cross-origin isolation を有効化できる、というわけです。
COEP: credentialless
の時でも、crossorigin
属性を付加するなどしてリクエストすることで、明示的に認証情報を送ることもできます。
参考: Load cross-origin resources without CORP headers using COEP: credentialless
Chrome のみ、96 から利用が可能です。
anonymous iframe #
同様の考え方で、iframe についても認証情報を送らないことでサードパーティーリソースを危険に晒さない anonymous iframe という方法が検討中ですが、iframe 関連はブラウザのアーキテクチャ的に複雑なため、仕様も含めて目下開発中です。
COOP: same-origin-allow-popups-plus-coep
#
COOP: same-origin
を使うことで OAuth や支払いなどの popup ウィンドウを使った連携が壊れるという件についても、緩和策が検討されています。COOP: same-origin-allow-popups
を使った場合なら、自分の origin から開いたウィンドウとはコミュニケーションできるので、これを cross-origin isolation の条件にした方がいいのではないか、というアイディアです。
そのための専用モードとして COOP: same-origin-allow-popups-plus-coep
が検討されていますが、まだ検討初期段階にあります。
まとめ #
この記事ではブラウザで cross-origin isolation を有効にして SharedArrayBuffer
や高精細タイマーを使う方法を解説してきましたが、考えることが多くてとても複雑です。
今すぐにでも Firefox や Safari でも動作させたいということであれば、cross-origin なリソースの連携をある程度諦めて cross-origin isolation を有効にするという選択肢もありそうですが、とりあえず従来通り Chrome で動けばいいやということであれば、デプリケーショントライアル に登録してしばらく様子を見る、というのが今は得策と言えます。
この記事で解説した内容は 2020 年の Chrome Dev Summit でセッションビデオとして公開しています。
最後に、11 月 17 日に Chrome Dev Summit の一部として、この Spectre から Site Isolation、cross-origin isolation までの流れについて解説する 1 時間程度のワークショップを行います。
何か相談ごとがある方はぜひご参加ください (基本的に英語のセッションです)。
参考記事 #
- Making your website "cross-origin isolated" using COOP and COEP
- Why you need "cross-origin isolated" for powerful features
- クロスオリジンアイソレーションを有効にするためのガイド
- SharedArrayBuffer updates in Android Chrome 88 and Desktop Chrome 92
- SharedArrayBuffer オブジェクトに関するメッセージについての説明
- Chrome 92以降のSharedArrayBuffer警告に対するZOZOTOWNが実施した調査と解決策
Subscribe via RSS