9月12日、Soatokが「The V8 JavaScript Runtime Undermined My Constant-Time JavaScript Library」と題した記事を公開した。この記事では、V8 JavaScriptランタイムの内部実装が定数時間ライブラリのサイドチャネル耐性を破壊していたという脆弱性の発見と修正について詳しく紹介されている。
「定数時間」実装がV8に破られていた
暗号実装において、秘密鍵の比較や選択処理を「どれだけ時間がかかったか」から推測されないようにする定数時間(constant-time)実装は、基本中の基本だ。パスワード比較やHMACの検証など、タイミング差異から秘密情報が漏洩しうる処理では、条件分岐を排除したビット演算で処理を統一する。この手法はC言語などのネイティブ実装では広く実践されてきたが、JavaScriptで同様の保証を得ることは、JITコンパイルや型推論といった最適化が常に走るランタイムの存在によって格段に難しくなる。
Soatokは6年前、この概念をデモするTypeScript/JavaScriptライブラリconstant-time-jsを公開した。本人も「本番環境では使うな」と明記しており、このパッケージに依存している外部パッケージ数はゼロだという。だがその「デモ用」ライブラリに、ブリティッシュコロンビア大学の博士課程学生であるYayu Wangらの研究チームが本物のサイドチャネル脆弱性を発見した。
脆弱性の核心:V8のToBooleanが分岐する
問題の根は、V8が内部でbooleanを扱う方法にある。
constant-time-jsのselect関数は、条件分岐の代わりにビットマスクを使う「ブランチレス」な実装を採用していた。しかしYayu Wangらが調査した結果、!!returnLeftという二重否定による変換がV8内部のBuiltins_ToBooleanを経由し、そこでtrueとfalseのケースが異なるアドレスの命令を実行することが判明した。
さらに深刻なのが、データキャッシュレベルの漏洩だ。V8のtrueとfalseはそれぞれ読み取り専用ヒープ上の静的アドレスに配置されている:
static constexpr Tagged_t kFalseValue = 0xad;
static constexpr Tagged_t kTrueValue = 0xc9;
この差は64バイトのキャッシュライン境界をまたいでいる。つまり同一の命令アドレスからのロードでも、trueの場合とfalseの場合で異なるキャッシュラインへのアクセスが発生する。
研究チームのレポートより:
同じロード命令がtrueとfalseに対して異なるキャッシュラインにアクセスする。命令アドレスが同一であっても、データオペランドが2つの異なるキャッシュラインを通じて条件値を暴露する。つまり実行命令トレースだけを見ると定数時間に見えても、命令アドレスとデータアドレスの両方を考慮しなければならない。
これは重要な示唆を含んでいる。命令トレースだけでは定数時間を保証できない——データキャッシュアクセスパターンも攻撃面になりうる、という点だ。
修正の方針:booleanを避ける
検証の結果、Soatokが導いた結論はシンプルだった:
観察:booleanを使わないことだけが安全への道だ。
V8のToBooleanの分岐は、V8の内部実装に深く根ざしている。JavaScriptライブラリ側でV8のランタイム挙動をコントロールする手段はない。ならば、そもそもbooleanを経由しない実装に切り替えるしかない。
修正版となるv0.5.0では、条件選択をbooleanへの変換を介さない純粋な整数演算で書き直し、Yayu Wangらによるテストでも漏洩が除去されたことが確認された。
タイムライン
- 2026-08-21:開示メール受信
- 2026-08-25:報告を検証し、パッチを作成
- 2026-08-26:提案パッチをメールで共有
- 2026-09-09:Yayu Wangが「パッチで漏洩が除去された」と確認
- 2026-09-10:公開開示・新バージョンリリース
- 2026-09-12:本ブログ記事公開
脆弱性の開示からパッチ確認まで約3週間。責任ある開示(Responsible Disclosure)の手順として整然としたプロセスだ。セキュリティアドバイザリはGHSA-pgf9-4q65-hrqjとして公開されている。著者は「もし誰かが非公開のシステムでこのコードを使っているなら、npm auditやアドバイザリの通知が内部のセキュリティ機構をトリガーして、最新版への更新を促すはずだから」と説明している。
JavaScript上の定数時間実装の限界
今回の件が改めて示したのは、「アルゴリズムレベルの定数時間」とランタイムレベルの保証は別物だという現実だ。JavaScriptはJITコンパイル、型推論、ガベージコレクションといった最適化が常に走る環境で動作する。暗号用途で定数時間保証が必要な場面では、WebAssembly経由でより低レイヤーな制御を行うか、ランタイム自体の挙動を信頼の前提として受け入れるかの選択を迫られる。「ブランチレスに書いた」だけでは不十分であり、データキャッシュアクセスパターンまで含めた検証が必要だという教訓は、JavaScriptに限らず、あらゆるマネージドランタイム上の暗号実装に当てはまる。
詳細はThe V8 JavaScript Runtime Undermined My Constant-Time JavaScript Libraryを参照していただきたい。