BitVisor T2セキュリティチップ搭載機対応状況(現状)

BitVisorのT2セキュリティチップ搭載機対応状況をご紹介します。

# Secure Boot関連

T2セキュリティチップ搭載機にはSecure Bootが実装されましたが、BitVisorでは特に何もしていません。他のUEFI搭載PCと同様、ファームウェアの設定で無効にしておけば動作します。独自の認証局を登録する機能があるのかどうかはわかりません。

# VT-d関連

BitVisorではコンパイル時の設定CONFIG_DISABLE_VTDにより、ACPIのDMARテーブルを隠すとともに、VT-dを無効にしていますが、T2セキュリティチップ搭載機では、EFIファームウェアがHPETのタイマー割り込みを使用するとともに、VT-dの割り込みremapping機能も使用しているようです。このため、単純にVT-dを無効にするとタイマー割り込みが発生しなくなってしまい、その後のEFIファームウェアの動作に支障をきたすことが分かっています。例えば、キー入力ができなくなります。

これの対策のため、割り込みremapping機能が実際に使用されているかどうかをチェックし、使用されている場合はアドレス変換のみを無効にし、ExitBootServices()のタイミングで割り込みremapping機能も含めて無効にしています。

この対策は以下の修正に含まれています:

https://bitbucket.org/bitvisor/bitvisor/commits/09704fb0e3579ccb5a26ea1c0acc6afdba7f670b

# 無線LAN関連

上述のVT-d対応によりBitVisorは起動しますが、無線LANデバイスドライバーが動作しません。VT-dが関係しているようですが、原因は不明で、解決していません。

# NVMe関連

T2セキュリティチップ搭載機にはNVMeデバイスのハードウェア暗号化機能が搭載されており、その関係で以下の問題がありました:

* Partition DriverのDisconnectController()が失敗する問題
* NVMeデバイスドライバーが特殊なI/Oコマンドを使用する問題

## Partition DriverのDisconnectController()が失敗する問題

DisconnectController()は、コントローラー(デバイスハンドル)からドライバーを切り離すEFIファームウェアの機能です。失敗するのは、ハードウェア暗号化機能が使えるNVMeデバイス全体を指すコントローラーから、Partition Driverを切り離そうとする時です。これが失敗するため、NVMe全体を指すコントローラーからのDisk I/O Protocolの切り離しも失敗します。NVMeデバイスドライバーを切り離すのには成功してしまいますが、その下のコントローラーが生き残っているため、再接続しても正常に動作しません。この原因は、ファームウェアに含まれるPartition Driverにあります。

一般に、UEFIのドライバーにはStart()とStop()があり、Start()でInstallMultipleProtocolInterfaces()を使用してプロトコルをインストールし、Stop()でUninstallMultipleProtocolInterfaces()を使用してプロトコルをアンインストールします。

Partition Driverの仕事は、以下のEDK IIの実装を見ると、基本的にはストレージ全体のデバイスハンドルに対して、ストレージが含む各パーティションを調べ、それらのデバイスハンドルを子供として作成し、そこに各プロトコルを登録しているようです。また、各プロトコルは、LBAをストレージ全体のものに読み替えて、ストレージ全体を指す親ハンドルのプロトコルに変換するようです。

https://github.com/tianocore/edk2/blob/559a07d84e5af3db09ae91844e4cb924b8f60668/MdeModulePkg/Universal/Disk/PartitionDxe/Partition.h
親ハンドルのプロトコルのインターフェイスを指すParentBlockIoなどのキーワードがあります。

T2セキュリティチップ搭載機のEFIファームウェアに含まれるPartition Driverは、ハードウェア暗号化機能が使える場合、Inline Cryptographic Interface Protocol (BlockIoCryptoProtocol)というプロトコルのインストール・アンインストールも行うようです。これはNVMe全体を指すデバイスハンドルにもインストールされており、ハードウェア暗号化機能で暗号化されたボリュームへのアクセスの際に使用されるようです。関数ポインターを差し替えてその呼び出しを観察すると、どうやらPartition Driverにある同プロトコルの仕事は、やはりLBAをNVMe全体のものに読み替えて、NVMe全体のプロトコルに変換することのようです。つまり、そこにはPrivate->BlockIoCryptoとPrivate->ParentBlockIoCryptoが存在することが容易に想像できます。

問題は、アンインストールの際にInline Cryptographic Interface Protocolのインターフェイスの指定が間違っていて、アンインストールが失敗することです。UninstallMultipleProtocolInterfaces()の関数ポインターを差し替えて引数を観察すると、問題のインターフェイスのポインターは、正しいポインターより0x80バイトだけズレていて、その先には親ハンドルのInline Cryptographic Interface Protocolのインターフェイスが入っていることがわかります。つまり、どうやらこのファームウェアを書いた人は、&Private->BlockIoCryptoと書かなければならないところに、間違えて&Private->ParentBlockIoCryptoと書いてしまったようです。(構造体メンバーの名前は推測ですが。) バグですね。

対策は、UninstallMultipleProtocolInterfaces()の関数ポインターを差し替えて引数を修正します。

この対策は以下の修正に含まれています:

https://bitbucket.org/bitvisor/bitvisor/commits/31e60def54732b0e6bd55424ad4f288376f1746b

## NVMeデバイスドライバーが特殊なI/Oコマンドを使用する問題

ハードウェア暗号化機能の関係で何らかの拡張が入っているだろうということは容易に想像できますが、実際に、通常64バイトのI/Oコマンドに128バイトが使用されています。ハードウェア暗号化機能を使用するのに128バイトが必要なのはともかく、ハードウェア暗号化機能を使用しない場合でも、64バイトのI/Oコマンドを使用するとシステムがリセットされてしまうようです。そのため、128バイトのI/Oコマンドに対応する必要があります。

また、Linuxなどのオペレーティングシステムに含まれるnvmeデバイスドライバーも、やはり64バイトのコマンドを使用するようですので、その対策として、ゲストオペレーティングシステムには通常のNVMeとして見せつつ、実際には128バイトコマンドを使用する機能も実装されました。driver=nvme_apple,ans2_wrapper=1とすれば本機能が有効になり、ハードウェア暗号化機能が使用されたボリュームにはアクセスできなくなる代わりに、Linuxのnvmeデバイスドライバーが使えるようになります。

この対策は以下の修正に含まれています。

https://bitbucket.org/bitvisor/bitvisor/commits/bf94819c6b5b0177e7ba76ed7b56fd36be8eb5f0

なお、メーカーの資料によれば、ハードウェア暗号化機能が使用する鍵はT2セキュリティチップに保持されていて、x86 CPUからはアクセスできないそうです。x86 CPUから見た時、NVMeはT2セキュリティチップの向こう側にあって、x86 CPUは一切鍵に触れることなく、暗号化や復号が行われることになります。(暗号化や復号には、専用のNVMeコマンドを使用する必要があるようですが、詳細は不明です。) また、オペレーティングシステムの暗号化機能をオンにしていなくても、APFSボリュームをフォーマットすると、最初から暗号化された状態になっています。"Encrypted at rest"と表現されます。暗号化されていないボリュームを作成するには、BitVisorのans2_wrapper機能を使用して、ハードウェア暗号化機能を隠蔽するしかなさそうです。

暗号化済みデータをそのまま読み出すことに特に制約はないようですが、鍵が取り出せないのですから復号はできないでしょう。暗号化済みデータは別パーティションに複製しても読むことができますが、それぐらいしか使い道はなさそうです。当然、別のT2セキュリティチップ搭載機に暗号化済みデータを持っていっても、鍵が違うので正しく復号することはできません。