投機的実行に基づくマイクロアーキ攻撃(Spectre / Meltdown)
CPU の高速化の仕組みそのものが秘密を漏らす。投機実行が残すキャッシュ痕跡からカーネルや他プロセスのメモリを読み出す原理と、KPTI など緩和策の代償が分かる。
- 1.Meltdown / Spectre は、CPU の投機的実行・アウトオブオーダ実行が、本来アクセスできないデータを一時的に読み、その値をキャッシュ状態に変換して残す副作用を突く。アーキテクチャ上は捨てられた計算の痕跡をマイクロアーキ層から読み出す。
- 2.Meltdown は権限例外を投機実行が追い越してカーネルメモリを読む(主に Intel 系)。Spectre は分岐予測を汚染し、被害者コード自身に境界外参照(バリアントV1)や任意ガジェット実行(V2)をさせる、より広範で根治しにくい欠陥。
- 3.緩和は KPTI(カーネルとユーザーのページテーブル分離)、retpoline、マイクロコード更新、LFENCE 挿入など。いずれも性能低下と引き換えで、Spectre V1 はソフトのみでの完全防御が難しく境界チェックの個別修正が要る。
なぜ「速くするための仕組み」が穴になるのか
Meltdown と Spectre(2018年公開)は、特定のバグではなく、現代 CPU の性能を支える根本的な設計――投機的実行(speculative execution)とアウトオブオーダ実行(out-of-order execution)――が原理的に持つ欠陥です。CPU はメモリ待ちで止まらないよう、後続命令の結果を先に計算しておく。分岐の行き先が確定する前に予測で先へ進み、外れたら結果を破棄します。
問題は、破棄されるのはアーキテクチャ状態(レジスタやメモリの公式な値)だけで、その投機中に動いたキャッシュなどのマイクロアーキ状態は巻き戻らない点です。つまり「なかったことにした計算」が、キャッシュに載ったか否かという形で痕跡を残す。攻撃者はこの痕跡を、サイドチャネル攻撃で定番のキャッシュタイミング計測で読み出します。
ISA(命令セット)が定義する「正しい計算結果」がアーキテクチャ状態です。投機が外れればここは完全に元へ戻ります。一方、キャッシュ・分岐予測器・TLB・実行ポートの混雑といった実装の都合で生じる内部状態がマイクロアーキ状態で、これは性能のため意図的に残されます。Spectre/Meltdown は「アーキ的には起きなかったが、マイクロアーキ的には起きた」読み取りを観測する攻撃だと捉えると本質が見えます。
痕跡の作り方:秘密をキャッシュに「符号化」する
両攻撃に共通する核は、読めた秘密の値そのものではなく、その値を使ったメモリアクセス位置をキャッシュに刻むことです。攻撃者は事前に大きな配列 probe[256 * 4096] を用意し、キャッシュから追い出しておきます。そして投機中に、盗んだ1バイト secret を使って probe[secret * 4096] に触れさせる。* 4096(ページサイズ)はキャッシュライン同士の干渉(プリフェッチ)を避けるための間隔です。
投機実行中(本来は許されない読み取り):
secret = *(kernel_or_oob_pointer) // 一時的に値が読める
tmp = probe[secret * 4096] // secret の値の場所だけキャッシュに載る
投機が巻き戻った後(アーキ的には何も起きていない):
for i in 0..255:
t = 時間計測( probe[i * 4096] を読む )
if t が速い: // ここだけキャッシュに残っている
secret = i // 秘密の1バイトが復元できた
巻き戻りで secret も tmp も消えますが、probe のうち「secret 番目のスロット」だけはキャッシュに残る。後から256個のスロットを総当たりで計測し、一つだけアクセスが速いインデックスが秘密の値です。これを Flush+Reload と呼びます。秘密を1バイトずつずらして繰り返せば、任意長のデータを抜けます。
Meltdown:権限例外を投機が追い越す
Meltdown(CVE-2017-5754)は主に Intel CPU を直撃しました。ユーザープロセスがカーネル空間のアドレスを読もうとすると、権限チェックで例外(フォルト)が発生して読み取りは失敗するはずです。ところが脆弱な実装では、例外の確定処理が完了する前に、アウトオブオーダ実行が後続命令を投機的に走らせてしまう。その短い窓の間、カーネルのデータが一時的にレジスタへ読まれ、上記の手順でキャッシュに符号化されます。
raise_exception(); // ← この権限例外は最終的に発生するが…
secret = *(kernel_address); // 例外確定前に投機実行で値が読めてしまう
probe[secret * 4096]; // その値をキャッシュへ符号化
// その後で例外がコミットされ、secret はアーキ的には破棄される
例外で命令はリタイア(正式完了)しないため、プログラム的にはアクセスは「失敗」です。しかしキャッシュ痕跡は残る。Meltdown が深刻だったのは、カーネルが物理メモリ全体を自アドレス空間にマップする慣行と組み合わさり、攻撃者がカーネル経由でマシン上のほぼ全メモリを読めた点です。
Meltdown の本質は権限分離そのものの破壊ではなく、権限チェックの結果が反映されるより先に投機実行が値を使ってしまうタイミング競合です。チェック自体は正しく行われ例外も上がる。だが投機実行はそれを待たずに走り、痕跡を残してから巻き戻る。だからこそ「アクセスは拒否された、しかしデータは漏れた」という直感に反する事態が起きます。
Spectre:分岐予測を汚染し、被害者に読ませる
Spectre は Meltdown より広く、根治しにくい欠陥です。Meltdown が「攻撃者プロセス自身が直接カーネルを覗く」のに対し、Spectre は被害者プロセス(カーネルやサンドボックス内のコードを含む)の分岐予測器を攻撃者が訓練して汚染し、被害者自身に不正な投機実行をさせる。代表的な2バリアントを押さえます。
Spectre V1:境界チェックバイパス(CVE-2017-5753)。 次のような安全に見えるコードが標的です。
if (x < array1_size) { // 境界チェック
y = array2[array1[x] * 4096]; // x が範囲内のときだけ実行されるはず
}
攻撃者はまず正当な x(範囲内)で何度もこの分岐を通し、分岐予測器に「この if は成立する(taken)」と学習させます。次に範囲外の x を渡すと、array1_size の読み込みがメモリ待ちで遅延している隙に、CPU は予測に従い境界チェックを投機的に飛び越え、array1[x](境界外)を読んで array2 経由でキャッシュに符号化します。チェックが「不成立」と確定すると投機は巻き戻りますが、痕跡は残る。コードは完全に正しいのに、CPU の予測が境界を踏み越えるのが V1 の怖さです。
Spectre V2:分岐ターゲットインジェクション(CVE-2017-5715)。 間接分岐(jmp [reg] のように飛び先が実行時に決まるもの)の予測先を攻撃者が汚染します。攻撃者は自プロセスで、被害者の間接分岐と同じ予測器エントリに当たるアドレスで分岐を繰り返し訓練し、予測先を任意の「ガジェット」(秘密をキャッシュに符号化するコード片)へ向けさせる。被害者がその間接分岐に達すると、汚染された予測により被害者のアドレス空間内のガジェットが投機実行され、秘密が漏れます。ROP に似ますが、実際にはリタイアしない投機実行内で完結する点が異なります。
| 観点 | Meltdown | Spectre V1 | Spectre V2 |
|---|---|---|---|
| 突く機構 | 例外と投機の競合(アウトオブオーダ) | 条件分岐の予測(境界チェック) | 間接分岐ターゲットの予測 |
| 誰が不正アクセスするか | 攻撃者プロセス自身 | 被害者コード(予測で境界越え) | 被害者コード(注入されたガジェット) |
| 主な影響範囲 | カーネル/物理メモリ全体 | 同一アドレス空間(サンドボックス突破等) | 同一アドレス空間の任意ガジェット |
| 主な対象 | Intel(一部 Arm) | 広範(ベンダ横断) | 広範(ベンダ横断) |
| 代表的緩和 | KPTI/ハード修正 | 個別の境界クランプ・LFENCE | retpoline・IBRS/IBPB・eIBRS |
緩和策とトレードオフ
Meltdown には KPTI(Kernel Page-Table Isolation)。 従来は性能のためカーネルとユーザーのページテーブルを共有し、システムコール時に切り替えを省いていました。KPTI はこれを分離し、ユーザーモード時はカーネルのマッピングを(ごく一部のトランポリンを除き)ページテーブルから外します。投機実行でカーネルアドレスを読もうとしてもマップ自体が無いため痕跡を作れません。代償は、システムコールや割り込みのたびにページテーブル切り替えと TLB フラッシュが増えること。syscall 主体の I/O 集約ワークロードでは数%〜場合により二桁%の低下が出ます。後継世代の Intel CPU はハードでこの読み取りを塞ぎ、KPTI 不要になりました。
Spectre V1 には個別の境界クランプ。 V1 は「正しいコードが予測で踏み越える」ため、一律のソフト緩和が効きにくい。対策は危険な分岐を個別に直すことです。境界チェック後のインデックスを投機時でも安全側に丸める(array1_index_mask でクランプする array_index_nospec 等)、あるいは投機を止めるシリアライズ命令 LFENCE を分岐直後に挿入します。LFENCE は以前の命令完了まで後続をブロックするので、闇雲に入れると性能を大きく削ります。
Spectre V2 には retpoline とマイクロコード。 retpoline は間接分岐を、予測器を使わない ret ベースの構造(call/ret で安全なループへ誘導)に置き換え、攻撃者が予測先を注入できなくするソフト技法です。加えて Intel/AMD はマイクロコード更新で IBRS / IBPB / STIBP(特権境界やスレッド間で分岐予測状態を分離・消去する制御)を提供し、後の世代は eIBRS としてハードに取り込みました。これらも分岐予測の自由度を削るため性能と引き換えです。
SMT(ハイパースレッディング)では2論理コアが分岐予測器やキャッシュなど多くのマイクロアーキ資源を物理的に共有します。これが Spectre V2 や後続の MDS/L1TF 系で同居スレッド間の漏洩経路になり得ます。最も確実な緩和が「SMT を無効化する」ことになる場面があり、その場合スループットを犠牲にします。マルチテナントなクラウドで秘密を扱うなら、最小権限・多層防御の発想で、信頼境界を跨ぐコアの共有自体を避ける配置が選択肢になります。
実務での教訓
第一に、これらは設計レビューやソースコード監査だけでは見えない層の脆弱性です。コードは正しく、権限チェックも存在する。漏れはコンパイル後・CPU の中で起きるため、対策はカーネル・コンパイラ・マイクロコード・ハードの全層に分散します。利用者にできる最大の防御は、OS とマイクロコードを最新に保つことに尽きます。
第二に、緩和はほぼ常に性能とのトレードオフです。KPTI、retpoline、SMT 無効化はいずれも速度を削る。脅威モデル次第では一部を緩める判断もあり得ますが、その判断は暗号の基礎同様、推測でなく計測(ベンチマークと攻撃成立可否の検証)に基づくべきです。
第三に、信頼境界をまたいで投機実行に秘密を触れさせない設計が王道です。ブラウザは異なるサイトを別プロセスに隔離(サイトアイソレーション)し、JIT は高分解能タイマーを鈍らせて Flush+Reload を妨害しました。新しい変種(MDS、L1TF、Retbleed、Downfall など)は今も登場するため、本番環境ではペネトレーションテストや CPU ベンダの勧告追跡を通じ、「自社の構成で何が成立し得るか」を継続的に見直すことが欠かせません。
(1) 原理は投機実行が巻き戻してもキャッシュ等のマイクロアーキ状態は残ること。(2) 秘密は値そのものでなくアクセス位置としてキャッシュに符号化され、Flush+Reload で読み出す。(3) Meltdown=権限例外を投機が追い越す(主に Intel)、対策は KPTI。(4) Spectre V1=境界チェックを予測で踏み越える、V2=間接分岐の予測先を汚染。対策は境界クランプ/LFENCE、retpoline/IBRS。(5) 緩和はすべて性能とのトレードオフで、V1 はソフトのみの一般的封じ込めが困難。
セキュリティ Article
投機的実行に基づくマイクロアーキ攻撃(Spectre / Meltdown)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Spectre
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 6
導入後に効く点
Meltdown は権限例外を投機実行が追い越してカーネルメモリを読む(主に Intel 系)。Spectre は分岐予測を汚染し、被害者コード自身に境界外参照(バリアントV1)や任意ガジェット実行(V2)をさせる、より広範で根治しにくい欠陥。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Spectre / Meltdown」に近いか確認する。
- 強みである「Meltdown / Spectre は、CPU の投機的実行・アウトオブオーダ実行が、本来アクセスできないデータを一時的に読み、その値をキャッシュ状態に変換して残す副作用を突く。アーキテクチャ上は捨てられた計算の痕跡をマイクロアーキ層から読み出す。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。