HN 日本語サマリー

← 一覧へ戻る
プログラミング

V8 JavaScriptランタイムが私の定数時間JavaScriptライブラリを損なう

The V8 JavaScript Runtime Undermined My Constant-Time JavaScript Library (soatok.blog)

15 pointsby some_furry10 コメント

要約

筆者が6年前に公開した定数時間JavaScriptライブラリ「constant-time-js」に、V8 JavaScriptランタイムの最適化によってサイドチャネル脆弱性が生じることが判明しました。この脆弱性は、V8のToBooleanメカニズムや、-0と-1の異なる扱い、および真偽値オブジェクトへの直接アクセスに起因し、キャッシュ動作を通じて秘密情報が漏洩する可能性がありました。筆者は修正パッチをリリースし、CVEの申請を行いました。

全文翻訳

6年前、私は「Soatok’s Guide to Side-Channel Attacks」と題したブログ記事を書き、暗号アプリケーションにおけるサイドチャネルの一般的なトピックと、それらを回避する方法について、PHPのサンプルコードを用いて議論しました。そのページで議論されたアルゴリズムは、セキュリティ目標を損なうコンパイラやランタイムの最適化を排除できないことを指摘しました。アルゴリズム的な定数時間を達成することはできても、それ以上の保証は、行われている作業の範囲外でした。そのため、このブログ記事の期間中は、アルゴリズム的な定数時間で十分であると仮定します。もしあなたの脅威モデルがこの仮定を受け入れることを妨げるのであれば、ご自身で追加の努力を行い、結果を教えていただければ幸いです。結局のところ、趣味でブログ記事を書いているファーリーとして、形式検証における大規模な研究プロジェクトの予算は持ち合わせていません。Soatok’s Guide to Side-Channel Attacks (2020年8月) 実演しやすくするために、デモンストレーション目的で「constant-time-js」という別のTypeScript / JavaScriptライブラリも書きました。長年にわたり、このデモコードはNPMによると(少なくとも現時点では)ゼロ個の依存パッケージに採用されています。本番環境でこれに依存しないようにという私の警告は効果がありました! しかし、それはオープンソースの依存関係のみをカバーしています。一部のプロプライエタリソフトウェアが私の設計に基づいて構築することを決定したかどうかはわかりません。先月、ファーリーコンベンションで、Yayu Wangという博士課程の学生から、constant-time-jsにおけるサイドチャネルの脆弱性について開示するメールを受け取りました。その後、ライブラリのバージョン0.5.0をリリースし、報告された問題を修正し、GitHubのセキュリティアドバイザリ機能を使用してCVEを要求しました。完全なクレジットは以下の通りです:脆弱性の発見および報告者:Yayu Wang。研究者:Yayu Wang、Kjell Dankert、Duy Kha Dinh、Aastha Mehta(ブリティッシュコロンビア大学)。 私がアドバイザリを投稿し、CVEを要求した理由は単純です。もし誰かが実際にこのコードを使用している場合(例えば、私が可視性を持っていないプロプライエタリソフトウェアシステムで)、npm auditまたはCVEの割り当ては、内部セキュリティメカニズムをトリガーし、可能な限り早く最新バージョンにアップグレードするよう促す可能性が高いです。緊急の対応(アドバイザリ、パッチなど)はすでに他で行われているため、この発見のより興味深い部分とその修正について、より深く掘り下げるブログ記事を書こうと思いました。なぜなら、正直に言って、私のブログを読む人は、あなたがオタクであるか、オタクなサブカルチャーの一員であるかのどちらかでしょうから、少しオタクになりましょう。 目次 タイムライン レポートの検証 パッチの作成 修正の検証 より高い保証はどのようなものになるか? タイムライン 2026年8月21日:開示メール受信(16:51)。Yayuにのみ返信(16:54)、「メールを受け取りました。現在旅行中で、火曜日までキーボードに触れません。できるだけ早くフォローアップします。」 2026年8月25日:レポートを検証し、パッチを作成。 2026年8月26日:開示メールに全員返信で、ローカルテスト済みの提案パッチを送信。 2026年9月9日:Yayuから、パッチをテストし、リークが除去されたことを確認したとの返信。 2026年9月10日:公開開示と新バージョンのタグ付け。 2026年9月11日:この記事の執筆。 レポートの検証 AI駆動の脆弱性ハンティングの時代において、深刻度のインフレと頻繁な幻覚が際立つ中、偽のレポートと正当なレポートの割合は著しく増加しています。バグが実際に存在し、研究者が主張するほど深刻であることを検証することは、メンテナーにとって常に重要でしたが、ゴミに溺れている場合はさらに強調されます。修正のためのプルリクエストの説明に、初期レポートメールが共有されていますので、全文を読みたい方は参照してください。説明からの関連抜粋は、どこから探し始めればよいかを判断するのに十分です:constant-time-jsの条件選択関数は、分岐のないJavaScript式を使用して選択マスクを構築します。select、select_alt、select_intsは、それでもV8の命令キャッシュおよびデータキャッシュの動作に秘密依存の動作を生じさせることがわかりました。リークは、V8のToBooleanメカニズム、-0と-1の異なる扱い、および異なるキャッシュライン上の生のtrueおよびfalse Oddballオブジェクトへの直接アクセスから生じます。結果として生じるキャッシュアクセスパターンは、選択条件の値を明らかにすることができます。Yayuの開示メール 明らかに、最初のチェックポイントはV8のToBooleanメカニズムです。これは以下のようになっています: // ToString // アキュムレータを文字列に変換します。 IGNITION_HANDLER(ToBoolean, InterpreterAssembler) { TNode<Object> value = GetAccumulator(); TVARIABLE(Boolean, result); Label if_true(this), if_false(this), end(this); BranchIfToBooleanIsTrue(value, &if_true, &if_false); BIND(&if_true) { result = TrueConstant(); Goto(&end); } BIND(&if_false) { result = FalseConstant(); Goto(&end); } BIND(&end) { SetAccumulator(result.value()); Dispatch(); } } おお、BranchIfToBooleanIsTrue()。その名前は、分岐サイドチャネルが公開されることを示唆しているように聞こえます。そして、実際にそれが観察されるものです。次に、ブール値からの変換を見る必要があります。Yayuが観察しているように:selectの場合、!!returnLeft変換がV8のBuiltins_ToBooleanに到達することが観察されました。trueおよびfalseのケースは、ビルトイン内の異なるアドレスで命令を実行します。ハードウェア読み取りウォッチポイントを別に使用して、V8がブール条件を変換する際のデータアドレスを測定しました。これにより、生のtrueおよびfalse Oddballオブジェクトへの直接読み取りがV8の読み取り専用ヒープで確認されました: 条件オブジェクト ベース to_number フィールドを含む 64 バイト ライン true 0x...0c8 0x...0cc 0x...0c0 false 0x...0ac 0x...0b0 0x...080 変換は、rdi が条件の Oddball オブジェクトを指している状態で、以下の形式のロードを実行します: movl rdx, [rdi - 0x1] // Oddball のマップをロード vmovsd xmm0, [rdi + 0x3] // Oddball の to_number フィールドをロード これにより、同じロード命令が true の場合は 0x...0cc、false の場合は 0x...0b0 にアクセスします。命令アドレスは同じでも、そのデータオペランドは2つの異なるキャッシュラインを通じて条件を明らかにします。これは、実行命令トレースだけでは操作を定数時間と誤って分類できることを意味します。命令とデータの両方のアドレスを考慮する必要があります。 ソースコードから観察可能ですが、true および false の値は静的に割り当てられています。 static constexpr Tagged_t kUndefinedValue = 0x69; static constexpr Tagged_t kNullValue = 0x85; static constexpr Tagged_t kempty_string = 0xa1; static constexpr Tagged_t kFalseValue = 0xad; static constexpr Tagged_t kTrueValue = 0xc9; これは、テーブルの 0x...0c8 から 1 ずれ、0x...0ac からも 1 ずれしていることを除けば、開示で報告された内容と近接しています。ずれの正確な理由はわかりませんが、おそらく無害なもの(バイトアラインメントやコンパイラ最適化など)でしょう。 さらに OddBall オブジェクトを調べることができます… @cppObjectLayoutDefinition @apiExposedInstanceTypeValue(0x83) @highestInstanceTypeWithinParentClassRange extern class Oddball extends PrimitiveHeapObject { to_number_raw: float64; to_string: String; to_number: Number; type_of: String; kind: Smi; } …および True/False サブクラス定義。 @cppObjectLayoutDefinition @hasSameInstanceTypeAsParent @doNotGenerateCast extern class Boolean extends Oddball {} @cppObjectLayoutDefinition @hasSameInstanceTypeAsParent @doNotGenerateCast extern class True extends Boolean {} @cppObjectLayoutDefinition @hasSameInstanceTypeAsParent @doNotGenerateCast extern class False extends Boolean {} これにより、内部的には true と false の両方が OddBall を継承していることがわかります。したがって、OddBalls の場合にのみトリガーされる分岐はすべて関連性があります。これは現時点では関連性はありませんが、後続のコードスニペットで覚えておく価値があります。 全体として、これはレポートのブール型側面を確認します。 観察:安全を確保する唯一の方法は、ブール値を避けることです。次に、見る必要があるのは…