Linux 6.9以降、LUKS suspendがディスク暗号化キーをメモリから消去できない問題
(mathstodon.xyz)- Linux 6.9以降、ノートPCのsuspend時にドライブをロックするツールが静かに失敗しており、LUKSのフルディスク暗号化キーがメモリに残っていた
- 原因は、2024年5月にLinux 6.9へ入ったブロックデバイスアクセスのリファクタリングと暗号化コードの予期しない相互作用で、提案されている修正は1行のパッチ
- 完全シャットダウンでは問題は表面化しなかったが、suspend-to-RAMではキーが残り、電源が入ったままのノートPCを確保した攻撃者がRAMからキーを取り出せる状態になっていた
- 発見は、Debianの
cryptsetup-suspendのNixOSポートを整理している最中に/proc/keysの項目を見つけたことから始まり、QEMUのメモリダンプで消去されているはずのvolume keyが残っていることを確認した - NixOSの統合テストとcryptsetupの警告パッチが提案されており、suspend直前のキー削除のようなセキュリティ機能は正常に見えても、実際のメモリ検証なしでは失敗を見逃しやすい
Linux 6.9以降のsuspend中にLUKSキーが残る問題
- Linux 6.9、つまり2024年5月以降、ノートPCのsuspend時にドライブをロックするツールが静かに失敗していた
- LUKSのフルディスク暗号化は、ノートPCの紛失・押収・盗難時にデータを保護するために使われるが、今回のケースではsuspend中も暗号化キーがメモリに残っていた
- 完全シャットダウンでは引き続き動作していたものの、ノートPCを完全に電源オフにせずsuspend-to-RAMで運用することは多く、その点で影響が大きい
- 電源が入った状態のノートPCを確保した人物がいれば、メモリに残ったキーが露出しうる状態だった
- Windowsで同目的のソフトウェアとしてVeraCryptに言及があったが、その後のコメントで“canonical software”は最も広く使われているソフトではなく、ITセキュリティ分野での代表的な推奨を意味すると訂正された
原因と1行のパッチ
- 原因は、Linuxカーネルのリファクタリングコミットである md: port block device access to file にあった
- 変更自体は妥当で有用なリファクタリングだったが、暗号化コードと長距離の相互作用を引き起こした
- 提案されている修正は 1行のパッチ
- パッチ作成者は、形式的検証なしにこのパッチが正しく、ほかの長距離相互作用がないとは言えないと述べている
- 再発防止のための後続作業も進められている
- NixOS自動テスト: 将来のリグレッションを検出するための統合テスト
- cryptsetupマージリクエスト: 静かに失敗せず、警告を出力するようにするパッチ
発見の経緯
- 発端は、Debianの
cryptsetup-suspendのNixOSポートを整理する作業だった - Debian本家とNixOSポートの両方に、ノートPCがときどきスリープできなくなる、煩わしいが有害ではない競合状態があった
- これを解決するため、Pali Rohárの未マージのカーネルパッチである dm-crypt suspend/hibernationキー削除パッチ を復活させようとした
- その過程でcryptsetupとカーネルのソースコードを調べ、文書上ではkeyringが呼び出しスレッドに紐づき、スレッド終了時に削除されることを確認した
- しかし、以前は知らなかった
/proc/keysに項目が見え、この点が疑念を深めた - 最終的にQEMU仮想マシンを起動してメモリをダンプし、消去されているはずのLUKS volume keyがそのまま残っていることを確認した
NixOS secure suspend-to-RAMプロジェクト
- 別途公開されている
secure-suspendプロジェクトは、NixOSで実験的なsecure suspend-to-RAMを提供する - 一般的なフルディスク暗号化では、ノートPCがsuspend状態のときキーがメモリに残るため、cold boot attackやRAM流出の手法に弱い可能性がある
- このプロジェクトは、Pali Rohárの古いカーネルパッチを復活させ、suspend時にLUKS暗号化キーを消去する方式を採る
- Debianの
cryptsetup-suspendに着想を得ているが、カーネルパッチを使うことで、ノートPCがときどきスリープできなくなる競合状態を避け、追加の予防策も加えている - 暗号化されたroot filesystemを完全にサポートし、統合テストも提供されている
- カーネルパッチとユーザー空間ツールは、ほかのLinuxディストリビューション向けにも調整して適用できる
- プロジェクトは secure-suspend として公開されている
suspendのセキュリティ検証が難しい理由
- suspend後に画面ロックが表示されても、ストレージデバイスが実際にロックされたとは限らない
- suspendから復帰した直後にディスクへそのままアクセスできるなら、ストレージデバイスは最初からロックされていなかったことが分かる
- たとえば、ロック画面の裏で動き続けるスクリプトでディスクアクセスを確認できる
- suspend後、SSH公開鍵で先に暗号化ストレージを解除しなくても接続できるなら、ストレージがロックされていないことを容易に確認できる
- UbuntuやDebianのデフォルト設定には、こうした保護を提供しようとする試み自体がなかったというコメントもある
- ストレージをロックしようとする試みが実際に正しく動作したかどうかは、別途検証する必要がある
- ログのタイムスタンプはsuspend前に生成されていても、wake後に記録された可能性がある
- 逆に、wake直後にシステム時刻が調整される前に生成されたログが、suspend時点の時刻のように見えることもある
- ストレージのロックがsuspend直前に行われたのか、resume直後に行われたのかは、ユーザー体験上は同じに見えても、セキュリティ上は決定的に異なる
- NixOSの統合テストは、仮想マシンでシステムを起動してメモリをダンプし、キーがsuspend時に実際に消去されたかを確認する方式になっている
1件のコメント
Hacker News のコメント
興味深いバグであることは確かだが、タイトルは少しクリックベイトのように感じる
私の理解では、
cryptsetup luksSuspendは公式にサポートされた機能というより、Debian が作った拡張に近く、このリグレッションの影響を受けたのも Debian だけではないかと思うサポートされているわけでも広くテストされているわけでもない機能について、カーネルを責められるのかはよく分からない
それでも印象的ではあるし、このリグレッションが再発しないようにテストが追加されたのは良いことだ。NixOSTests は本当に素晴らしいという OP の意見にも同意する
ただ、タイトルだけ見ると、特定のディストリビューション 1 つではなく広範に広がった問題のように見える
その通り。デフォルト設定を使っている人には影響しない。そもそも suspend 中にボリュームキーが安全だとは期待していないはずだからだ
Debian の解決策は複数の、おそらく大半の他のディストリビューションに移植されており、個人で移植を維持していた人もかなりいたと思う
thread-keyring(7)のマニュアルページは「thread keyring は、それを参照しているスレッドが終了すると破棄される」と約束しているcryptsetup プロジェクトは、ユーザー空間からカーネル空間へキーを渡す仕組みでこの性質に依存していたが、カーネル 6.9 がこの性質を壊すリグレッションを導入した
以前 Arch や openSUSE でも時々使ったことがあり、Debian 以外のディストリビューションにも確実に存在する
おそらく system suspend との自動統合を思い浮かべているのだろうが、それは本筋から外れている。
luksSuspendはシステムメモリからキーを消去すると文書化されており、Linux 6.9 の該当するリファクタリングパッチによってその動作が止まったただし実際には cryptsetup 側のバグとも見なせる。カーネル keyring キーの非常に具体的な寿命の挙動に依存していたためで、ユーザー空間でより明示的に消去すべきだったという主張も可能だ
[1]: https://gitlab.com/cryptsetup/cryptsetup/-/commit/3cea5dcc7b...
[2]: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/docs/v1...
[3]: https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/93...
おそらく
luksSuspendの後に実際に有用な形で RAM suspend を実行する仕組みのことを言っているのだと思うが、それは当初 Debian 向けで、その後 Arch にも入ったものの、どちらもデフォルトではなかった他の方法はあまり見当たらない。スリープ、つまり RAM suspend をすると、すべてが RAM に保存され暗号化されているが、マスターキーはカーネルメモリに残っていると記憶している
一方 hibernate、つまりディスク suspend をすると、RAM 全体の内容がマスターキーまで含めてディスクに書き込まれて暗号化され、RAM は消去される
再び起こすときには、マスターキーを復号してディスク内容をメモリに戻すためにパスフレーズを再入力する必要がある
しかし Debian は、選択機能である
cryptsetup-suspendアドオンを先に作っており、これはメモリからキーを消去することになっているluksSuspendコマンドを実行した後、resume 時にパスフレーズを再要求するカーネル 6.8 までは説明どおりに動作していたが、カーネル 6.9 以降は静かに動作しなくなった
この機能を有効にすれば、コールドブート攻撃は過去のものになる。通常は RAM 速度を約 0.5% 低下させるため、デフォルトで無効になっているだけだ
Sleep 後に起動パスワードを再入力しないのだから、暗号化キーがまだメモリに残っているのは明らかだ
cryptsetup-luksSuspendを使っていないのは明らかだこれは自分にとってはあまり気になる問題ではない
ディスク暗号化をする唯一の理由は、ノート PC を売るときに誰かが税務書類やクレジットカード情報を漁ることを心配しなくて済むようにするためだ
もちろんノート PC も消去するが、データがドライブレベルで暗号化されていれば、フォレンジックツールのようなものでデータを復元されるリスクは非常に小さいと見ている
LUKS は、ディスクを開くにはボリュームキー全体が必要になるアンチフォレンジック・アルゴリズムを使っている。キーブロック群を拡散アルゴリズムで結合し、XOR して実際のマスターキーを作る仕組みなので、理論上はボリュームキーの 1 セクターだけを消去しても全体が復元不能になるはずだ
つまり、キーのブロックが 1 つでも欠けていれば、残りを簡単に推測することはできないという意味だ
セキュリティ専門家ではまったくないが、最近「リファクタリング中にファイルをまたぐCのチェック1行を見落とした」ことで生じた致命的なセキュリティバグが定期的に見つかっているのを見ると、巨大で安全なオープンソースのCコードベースという前提そのものが疑わしく思える
Cだけの問題ではないが、特にCでは不変条件を一貫して強制し追跡するのがより難しく、コード変更時にはなおさらだと思う
不変条件を型にエンコードする関数型プログラミングが、現実的にスケールする解決策なのかも分からない。モデル検査? LLMファジング? 明確な境界を持つ、より少ない基本要素? seLinuxはそういう形で「検査」されたのだろうか?
本質的にはこういうことだ:
original:
DoTheThing()new:
DoTheThingSlightlyDifferentButKeepMyCredentialsAlive()fix:
DoTheThingSlightlyDifferentButDoInFactNOTKeepMyCredentialsAlive()経験上、厄介なバグのかなりの部分は上位レベルのシステム不変条件の違反から出てくるもので、これは自動化できる性質のものには見えない
Leanのようなものでも、プログラムが特定の性質を満たすことは証明できるが、その性質をまず思いついていなければならない。証明が不変条件を代わりに発見してくれるわけではない
関連するセキュリティ性質を思いついていたなら、回帰テストを書くのは難しくなかったはずだ。本当に難しい部分は、実装を安全に表現することではなく、実装が保存すべき性質があるという事実に気づくことだと思う
問題は、監査可能性が高いからといって自動的により多く監査されるわけではない点にある
十分な実力を持つ人たちが、十分な時間をかけて作業する必要がある
これは関心事が交差し、ドメイン横断の知識が不足していたために生じたバグだ。Lispやアセンブリ言語でもおそらく同じだったはずだ
言い換えれば、何がセキュリティ問題なのかについて厳密な定義がない
連邦機関が鍵を得る方法をどうしても必要としていたのか? これはバグドアなのか? コミットは追跡されたのか?
最近こういうパターンをよく見るので、少し疑わしく思い始めている。人々がこれにより敏感になって、より多く投稿しているからかもしれない
暗号化キーがメモリ上にあることが、ただちに抽出できることを意味するわけではない。本来あるべきでない場所に、不必要に無期限で残された、というのに近い
こういうリグレッションは、すべてが引き続き「動作」するため見落としやすい。セキュリティバグは自ら姿を現さないことが多い
書くのも楽しかったし、このバグを導入した具体的なカーネルのリファクタリングを見つけるために
git-bisectを走らせられるようにもしてくれた: https://github.com/NixOS/nixpkgs/pull/532499FedoraのノートPCでは、suspendから15分後にディスクへhibernateするようLinuxを設定している。メモリの電源を切ってしまえば、こういうDebian固有のバグは問題にならない
DebianのLinuxツール拡張は理論上は良いが、実際にコールドブート攻撃を心配するなら、LUKSキーだけでなくすべてのキーと重要な文書がメモリから消去される必要がある
だからコールドブートを防ぐまともな方法は、結局hibernateだけだ
知る限り、TPMを使わなければ実用的ではない。そしてTPMを使うなら、実質的にTPMに運命を委ねることになる
この脆弱性が商用OSにあったら、このHNスレッドがどう見えたか想像してみればいい
最上位コメントは間違いなく、Applosoftはもはやソフトウェア品質を気にしていないとか、「OSにバイブコーディングのゴミを許すとこうなる」という内容だったはずだ
その下のコメントは監視産業複合体とNSAに関する陰謀論だっただろう。他の場所なら狂った話だが、HNではそうではなかったはずだ
こんなに重要なものが、なぜ毎回のビルドでテストされていないのか分からない