SharedArrayBuffer とクロスオリジン分離の問題への対処の記録

Legalscape (リーガルスケープ) アドベントカレンダー 2021 の 12/20 (月) のエントリです。本日のエントリは、Legalscape が遭遇した SharedArrayBuffer とクロスオリジン分離の問題についてお送りします。

「何もしていないのに Legalscape が壊れました」

それはある夏の暑い日のことでした。

あるお客様からのお問い合わせで「Legalscape が突然使えなくなったんですが…」というご連絡をいただいた我々は「あれ? 今日って何かプロダクション環境にデプロイしましたっけ? フロントエンドかな? それともバックエンドの API サーバかな?」などと Slack で会話しながら、どういう問題が発生しているのかを具体的に知るために調査に取り掛かりました。

このときの我々は何も知りませんでした。何もしていないのに Legalscape が壊れたことに…

初期調査の結果は、環境依存で発生する問題であるように見えました。「僕の環境だと再現したんですけど、他の方はどうですか?」「いや、私の環境だと再現しないですね……」こんな調子で、あるメンバーの環境では確かに問題が再現するが、他のメンバーの環境では再現しない、我々開発者にとってはおなじみのやっかいな問題。

ひとまずフロントエンドとバックエンド API サーバのどちらに問題の原因があるのかあたりをつけるため、Chrome DevTools のコンソールタブとネットワークタブを開いて問題が発生する操作をしてみたところ、コンソールには以下のスタックトレースが表示されていました。

ReferenceError: SharedArrayBuffer is not defined
    at Object.<anonymous> (https://*****.legalscape.jp/*****/**********.js:2:*****)
    at c (https://*****.legalscape.jp/*****/**********.js:1:*****)
(以下略)

「あれ、なんで SharedArrayBuffer が not defined になっているの…?」「そもそも SharedArrayBuffer なんて使ってましたっけ?」「いや覚えがないですね」「というか昨日まで動いていたのになんで今日になって動かなくなったの?」「いやさっぱり心当たりがない…」

そんなこんなで我々は薄々気づき始めたのです。そう、どうやら何もしていないのに Legalscape が壊れたようであることに…

ひとまず SharedArrayBuffer という手がかりは得られたものの、これだけではなぜこの問題が発生したのかの原因究明には至りませんでした。そこに他のメンバーからの新たな情報提供が届きます。

「古い Chrome のバージョン 91 で動作確認したら再現しなかったけど、最新版のバージョン 92 に更新したら再現したっぽい」

そうです。ここまでお読みいただいて勘の鋭い方ならもうお気づきかと思いますが、かの Chrome 92 で必須となった、SharedArrayBuffer 利用時のクロスオリジン分離 (cross-origin isolation) 有効化の影響でこの問題が生じていたのでした。

そしてこの Chrome バージョン 92 という手がかりを得て、我々も遂に問題の本質に気づきました。何もしていないから Legalscape が壊れた、ということに…

Origin trial による暫定対処

問題の原因が明らかになったことで、我々が応急処置としてとるべき対処方法も徐々に明らかになっていきました。

まず最初に検討したのが、この問題の正攻法と思われるクロスオリジン分離1を達成して SharedArrayBuffer を再び使えるようにする方法です。しかし、この方法は明らかに影響範囲の調査や対応に時間がかかり過ぎることが想定できたために採用を諦め、早々に他の方法を模索することにしました。

続いて検討したのが、SharedArrayBuffer の利用を止める方法です。SharedArrayBuffer が使えないなら使わなければいいじゃん、という安易な発想ではありますが、これはこれで確実な手段となり得ます。しかし SharedArrayBuffer がどこで・どのライブラリで使われているのかを漏れなく確実に把握し、ライブラリを除去したりアプリケーションコードを書き換えたりするのは応急処置としてはやはり時間がかかり過ぎる懸念があったため、より暫定的ながらも容易に対処できる手段がないかどうか探すことにしました。

そして最終的にたどり着いたのが origin trial に登録する方法です。先の Chrome Developers のブログ記事にも、Chrome 92 までにクロスオリジン分離が間に合わない場合の対処法として案内されています。

実際に我々もこの origin trial に登録してトークンを発行し、フロントエンドを構成する HTML の meta 要素としてこのトークンを埋め込むことで Legalscape が壊れた状態を復旧するに至りました。

SharedArrayBuffer を利用するライブラリの除去による対処

さて origin trial のトークンを利用する対処方法はあくまで応急処置であり、トークンの有効期限が切れるまでの間により適切な処置をする必要がありました。

トークンの有効期間は約 150 日とそれなりに余裕はありましたが、ビジネス的に優先すべき他の課題の存在と、Legalscape のプロダクトのフロントエンドで利用している・利用する予定でいるサードパーティーのサービスにおけるクロスオリジン分離の対応状況が不明瞭でありそれなりの期間の調査を要することから、本命であるクロスオリジン分離の期限内の対応は難しいと判断しました。

したがって今回は、SharedArrayBuffer (を利用しているライブラリ) の利用を止める方法によって origin trial のトークンを除去し、暫定対処を解消することにしました。

Legalscape における SharedArrayBuffer is not defined の問題の発端となった具体的なライブラリは、whatwg-url が依存している webidl-conversions2 でした。しかし whatwg-url のライブラリ自体が Legalscape における利用用途的に必ずしも必要とよべるほどのライブラリではなかったため、このライブラリを除去しアプリケーションコードを少々修正して SharedArrayBuffer-free な状態を実現しました。

再発防止策

そもそもこの問題が発生した根本的な原因としては「Chrome 92 からクロスオリジン分離された環境でしか SharedArrayBuffer が使えないという事実を事前に把握できていなかった」ことと、「SharedArrayBuffer に知らず知らず依存していた」ことに尽きます。

したがって今後同様の事態を引き起こしてしまうことを防ぐために、以下の再発防止策を講じることにしました。

Chrome ベータ版を業務利用するブラウザとして推奨する

普段から安定版としてリリースされる前の Chrome をメンバーの多くが利用していれば、安定版よりも一足先にブラウザ側の仕様変更の影響を把握することができます。ゆえに強制はせずとも、業務中だけでも Chrome ベータ版を積極利用することを弊社では推奨しています。

Google Search Console からの通知を多くのメンバーが受け取れるようにする

今回の SharedArrayBuffer の件に限って言えば、ZOZO テクノロジーズ社 (社名はエントリ公開当時) のこちらの記事 にあるように、事前にメッセージを受けることができていれば問題を事前に把握し、適切な対処が打てた可能性があります。

そのため、Google Search Console にアクセスできる権限を見直し、できるだけ多くのメンバーがメッセージを受け取れるようにしました。

SharedArrayBuffer がプロダクションコードに含まれていることを検知する

今回の問題に特化した再発防止策になりますが、フロントエンドのコードから SharedArrayBuffer を除去したとしても、将来新たにライブラリを導入しようとしたときにそのライブラリ (およびその先にある依存ライブラリ) で SharedArrayBuffer をその利用可否チェックなしに利用しているようではまた問題が発生してしまいます。

そこでフロントエンドのビルド済みアセットの JavaScript ファイルに対し、SharedArrayBuffer を利用しているコードが混入していないかを CI の過程でチェックするようにしました。具体的には新たに用意した npm パッケージ sab-usage-detector によって、AST に変換された JavaScript コードに対して SharedArrayBuffer が識別子として存在しているかどうかをチェックするようにしています。

目指すべき未来

ここまで記してきたように、現時点では SharedArrayBuffer の利用を控えるという、どちらかというと防御的な対策によって「Legalscape が壊れる」事態を解消しています。しかしこれもまた origin trial トークンと同様に暫定対処でしかないと我々は考えています。

本来は SharedArrayBuffer の要否に関わらず、Spectre の脅威からユーザとサービスを守るためにクロスオリジン分離を推し進めていく必要があるものと考えています。そのためには Legalscape の制御下にある各種リソースの配信を同一オリジンに揃えたり、サードパーティーのサービスのクロスオリジン分離の対応状況を見極めて着実に進めていく必要があるのですが、如何せん人の手が足りておらずなかなか進められていないのが現状です。

そういうわけで、この記事をお読みになられてクロスオリジン分離に興味を持たれた方がいましたら、ぜひ以下のリンクのページからソフトウェアエンジニアのポジションに応募、もしくはカジュアル面談をお申し込みいただければと思います!

採用情報

最後までお読みいただきありがとうございました!


  1. 具体的なクロスオリジン分離の方法については、2021 年 12 月の現時点では Eiji Kitamura さんのエントリ が大変参考になります (我々も大いに参考にさせていただきました)。 

  2. より厳密には、クロスオリジン分離がなされていないブラウザ環境のように SharedArrayBuffer が利用できない環境を考慮せず、常に利用できるものとして同 API を参照しているバージョンの webidl-conversions に whatwg-url が依存していた、という話になります。webidl-conversions の 最新バージョン (2021 年 12 月時点) では、SharedArrayBuffer が利用できない環境を考慮した実装に修正されています。