長い記事なので先に結論を書きます。

Chrome および Firefox で SharedArrayBuffer や高精細タイマーが使えるようになりました。Safari もまもなくです。そのためには 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 を有効にすると、下記のことが可能になります:

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 です。

COEP: require-corp

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.comCOEP: 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.crossOriginIsolatedfalse になりますが、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 から利用が可能です。

COEP: credentialless

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 時間程度のワークショップを行います。

何か相談ごとがある方はぜひご参加ください (基本的に英語のセッションです)。

参考記事 #