Protected KVM (pKVM)
備考: pKVM は現在、実験的で開発中の機能であり、新しい分離機能が 実装されるにつれて、互換性を壊す変更が入る可能性がある。 質問がある場合は、kvmarm@lists.linux.dev の開発者に連絡してほしい。
概要
ホストカーネルを 'kvm-arm.mode=protected' で起動すると、「Protected
KVM」(pKVM) が有効になる。起動中に pKVM はホスト用のステージ 2 の
アイデンティティマップページテーブルを設定し、それを使って EL2 で動く
ハイパーバイザを、EL1/0 で動くホストの残りの部分から分離する。pKVM は
GICv3 だけをサポートする。保護モードでは GICv2 はサポートされず、
GICv5 ベースのゲストもサポートされない。
pKVM では、KVM_CREATE_VM ioctl() に KVM_VM_TYPE_ARM_PROTECTED
というマシンタイプ識別子を渡すことで、保護仮想マシン (pVM) を作成できる。
ハイパーバイザは、pVM がページにアクセスするたびにそのページをステージ 2
のアイデンティティマップからアンマップして、pVM をホストから分離する。
pVM が自分の IPA 空間の特定の領域をホストと共有して VMM と通信できるよう、
ハイパーコールが用意されている。Linux ゲストは、これらのハイパーコールを
発行するために CONFIG_ARM_PKVM_GUEST=y で設定されている必要がある。
詳細は hypercalls.rst を参照。
分離の仕組み
pKVM は、pVM をホストから分離するためにいくつかの仕組みに依存している。
CPU メモリの分離
状態: 匿名メモリとメタデータページの分離。
メタデータページ (ページテーブルページや 'struct kvm_vcpu' ページ
など) は pVM の作成時にホストからハイパーバイザへ寄付され、その結果、
pVM が破棄されるまでステージ 2 のアイデンティティマップからアンマップ
される。
通常の KVM と同様、ページはホストが処理するステージ 2 のページフォールト に応じて、遅延してゲストにマップされる。ただし pVM を実行するときは、 これらのページはまずピン留めされ、寄付の手順の一部としてステージ 2 の アイデンティティマップからアンマップされる。このため、保護されていない VM と比べていくつかユーザから見える違いが生じる。その主な原因は MMU ノーティファイアがないことである。
pVM の実行が始まると、メモリスロットは移動も削除もできない (
-EPERM)。読み取り専用のメモリスロットとダーティログはサポートされない (
-EPERM)。スワップを除き、ファイルにバッキングされたページを pVM にマップする ことはできない。
寄付されたページは
RLIMIT_MLOCKに対して計上されるため、VMM は 十分なリソース制限を持つか、CAP_IPC_LOCKを付与されていなければ ならない。実行時にメモリを回収する仕組みがないため、pVM のために ロックされたメモリは pVM が破棄されるまでロックされたままになる。VMM のアドレス空間への変更 (たとえばメモリスロットに対応するマッピング の上への
MAP_FIXEDmmap() ) はゲストに反映されず、一貫性が失われる おそれがある。共有し返されていない pVM のメモリにアクセスすると、SIGSEGV が配送 される。
システムコールが共有し返されていない pVM のメモリにアクセスすると、
-EFAULTを返すか、そのメモリページを強制的に回収する。回収された メモリはハイパーバイザによってゼロ埋めされ、その後 pVM でそのメモリに アクセスしようとするとKVM_RUNioctl() から-EFAULTが返る。
CPU 状態の分離
状態: 保護 vCPU の CPU レジスタ状態は完全に EL2 で管理される。
pKVM は保護 vCPU のコンテキストスイッチをハイパーバイザ内で完全に行う。 保護 vCPU の初期状態を設定するのはホストではなくハイパーバイザであり、 各エグジットで必要となるものだけが同期し戻される。
ユーザから見える影響は 保護 VM の API の振る舞い で説明する。
IOMMU を使った DMA の分離
状態: 未実装。
Trustzone サービスのプロキシ
状態: ホストからの FF-A 呼び出しと PSCI 呼び出しは pKVM ハイパーバイザが 代理する。
FF-A プロキシにより、ホストは「confused deputy」攻撃の一環として pVM や ハイパーバイザのメモリを Trustzone と共有できない。PSCI プロキシにより、 CPU がホストで実行しているときは常にステージ 2 のアイデンティティマップ が設定されている。このプロキシは、保護ゲストに提供される PSCI の扱いとは 別であり、そちらは 保護 VM の API の振る舞い で説明する。
保護 VM ファームウェア (pvmfw)
状態: 未実装。
保護 VM の API の振る舞い
保護 vCPU の状態は EL2 が所有する (CPU 状態の分離 を参照)。VMM は
最初の KVM_RUN より前に vCPU を設定する。その後は状態はゲスト専用に
なり、それにアクセスする ioctl は -EPERM を返す。vCPU の ioctl の
エラーは一つの規則に従う。-EPERM はホストがゲストの所有する状態を
求めたことを意味し、-EINVAL はその要求が保護 VM に対して有効でない
ことを意味する。また pKVM が保護 VM に対してサポートしない機能は、KVM が
実装されていない機能を拒否するのと同じように拒否される (たとえば
KVM_ARM_VCPU_PVTIME_CTRL からの -ENXIO)。ioctl 自体については
Documentation/virt/kvm/api.rst で説明する。
起動
保護 VM は単一の主 vCPU から起動する。最初の KVM_RUN より前に、
VMM は起動状態を用意する。
主 vCPU に
KVM_MP_STATE_RUNNABLEを、それ以外の vCPU にKVM_MP_STATE_STOPPEDを設定する。EL2 は保護 VM ごとに RUNNABLE な 主 vCPU を一つだけ許す。二つ目の RUNNABLE な vCPU は最初のKVM_RUNで失敗する。KVM_SET_ONE_REGで主 vCPU の起動状態を設定する。PCにカーネルの エントリポイントのアドレスを、x0に DTB のポインタを入れる。
EL2 がホストから受け取るレジスタは PC と x0 だけである。実行前の
その他の書き込みは受け付けられるが、ゲストはハイパーバイザがリセットした
状態から起動する。
副 vCPU はゲスト自身が PSCI CPU_ON で起動する (電源状態 を参照)。
CPU_ON がエントリポイントとコンテキスト ID を与える。VMM はそれらが
どこで起動するかを選べない。
vCPU の状態
vCPU が一度実行されると、
KVM_GET_ONE_REGとKVM_SET_ONE_REGは-EPERMを返す。それ以前は、EL2 がゲストの起動状態を組み立てる元に なるホスト側のコピーにアクセスする (起動 を参照)。KVM_ARM_VCPU_INITは保護ゲストがサポートする vCPU の機能だけを 受け付け、それ以外には-EINVALを返す。KVM_ARM_VCPU_PSCI_0_2は 必須である。EL2 がゲスト向けに PSCI 1.1 を実装しているためである (電源状態 を参照)。KVM_ARM_VCPU_EL1_32BITはサポートされない。 保護ゲストは AArch64 だけで動き、ID_AA64PFR0_EL1に AArch32 の サポートは見えない。vCPU が一度実行されるとKVM_ARM_VCPU_INITは-EPERMを返す。再初期化してもホスト側のコピーだけがリセットされる ためである。KVM_SET_VCPU_EVENTSは外部アボートの注入 (ext_dabt_pending) に対して-EPERMを返し、KVM_CAP_ARM_INJECT_EXT_DABTは保護 VM には提供されない。SError の注入は影響を受けない。KVM_SET_GUEST_DEBUGは-EPERMを返し、KVM_CAP_SET_GUEST_DEBUGは保護 VM には提供されない (デバッグ を 参照)。
電源状態
EL2 は保護ゲスト向けに PSCI 1.1 を実装している。vCPU の電源状態を変える
呼び出し CPU_ON と CPU_OFF は EL2 で処理され、ホストはその結果を
変えられない。CPU_ON では、ホストは対象のスケジュールを行うだけであり、
その対象は EL2 によってすでにゲストが選んだエントリポイントにリセット
されている。CPU_OFF では、ホストはスケジュールを止めるだけである。
AFFINITY_INFO はホストが処理し、問い合わせが対象とするすべての vCPU の
スケジュールを止めると OFF を返す。これは保護されていない VM と同じで
ある。vCPU は最初の KVM_RUN で有効な CPU_ON の対象になり、それ
より前は EL2 が INVALID_PARAMETERS を返す。プラットフォーム呼び出し
CPU_SUSPEND、SYSTEM_OFF、SYSTEM_RESET、SYSTEM_RESET2 は
ホストへ転送され、保護されていない VM と同じように動作する。SYSTEM_*
の呼び出しは KVM_EXIT_SYSTEM_EVENT として VMM へエグジットする。
SYSTEM_RESET2 のリセットタイプとクッキーはゲストのレジスタにあり、
vCPU が一度実行されると KVM_GET_ONE_REG はそれらを拒否する。その他の
関数は NOT_SUPPORTED を返し、PSCI_FEATURES も同じ集合を報告する。
転送された呼び出しはホストが処理するため、KVM_REG_ARM_PSCI_VERSION
への KVM_SET_ONE_REG は 1.1 未満のバージョンに対して -EINVAL を
返す。
vCPU が一度実行されると、その電源状態は VMM の PSCI 呼び出しではなく、
ゲストの PSCI 呼び出しに従う。ゲストが CPU_OFF で電源を切った vCPU、
またはまだ CPU_ON でオンラインにしていない vCPU は
KVM_MP_STATE_UNINITIALIZED として読め、ゲスト内の CPU_ON だけが
その電源を入れられる。そのような vCPU に対して KVM_SET_MP_STATE は
KVM_MP_STATE_RUNNABLE と KVM_MP_STATE_SUSPENDED には -EPERM
を返し、KVM_MP_STATE_STOPPED は受け付けてそのままにし、
KVM_MP_STATE_UNINITIALIZED を VMM から受け取ることはない。
KVM_MP_STATE_STOPPED は EL2 が電源を入れた vCPU を停止させ、
KVM_MP_STATE_RUNNABLE はそれを再開させるので、VMM は vCPU を一時停止
できる。
その他のインターフェースの違い
KVM_CHECK_EXTENSIONは pKVM が保護ゲストに対してサポートする arm64 のケイパビリティだけを報告し、KVM_ENABLE_CAPもそれらだけを 受け付ける。VM のファイルディスクリプタで問い合わせる。システムの ファイルディスクリプタには絞り込む対象の VM がない。汎用の KVM コード が自分で答えるケイパビリティ (たとえばKVM_CAP_SYNC_MMUやKVM_CAP_DEVICE_CTRL) はどの VM に対しても同じように報告され、その 機能が保護ゲストで動くことを意味しない。このフィルタはすべての インターフェースを対象にしているわけでもない。デバイス fd の設定 (たとえばKVM_CREATE_DEVICEの後の VGIC) と vCPU の属性は絞り込まれ ないため、フィルタが除外する機能はその属性自身によって拒否される。 たとえばKVM_ARM_VCPU_PVTIME_CTRLは、steal time が実装されていない 場所と同じように-ENXIOを返す。保護 VM の vGIC はホストが管理し続ける。デバイスの作成、設定、割り込みの 注入はいずれも保護されていない VM と同じように動作する。
KVM_ARM_SET_COUNTER_OFFSETとKVM_ARM_GET_REG_WRITABLE_MASKSは-EINVALを返す。これらのケイパビリティは保護 VM には提供されず、 保護 VM のカウンタオフセットと ID レジスタは EL2 が設定する。保護ゲスト には物理タイムベースが見える。保護ゲストが各メモリページに最初にアクセスするとホストへエグジットする。 ページがマップされる前は、ハイパーバイザがメモリとデバイスを区別 できないためである。ストアの場合は、ホストはシンドロームが示す レジスタの値をアクセス幅に合わせて切り詰めたものだけを見て、 レジスタファイルの他のものは見ない。ロードの場合は、シンドロームが示す レジスタがホストの返した値を取る。これはエミュレートされたアクセスと 同じである。
与えられていない機能を使う、または
SMCを実行する保護ゲストは、 ハイパーバイザから未定義命令例外を受け取る。ホストは関与しない。PSCI を除き、ハイパーバイザは保護ゲストの SMCCC 呼び出しを自分で処理 し、ホストを関与させない。実装していない関数は
NOT_SUPPORTEDを 返し、ホストの SMCCC フィルタは適用されない。VM デバイス属性の ioctl (KVM_ARM_VM_SMCCC_CTRL) は保護 VM に対して-EINVALを返す。ハイパーバイザはトラップされたゲストのアクセスを、CPU の命令シンドローム からのみデコードできる。このシンドロームは、単一の汎用レジスタの ロードまたはストアに対してのみ提供される。シンドロームのないアクセス (たとえばロード/ストアペアや SIMD/FP アクセス) はデコードできない。 保護されていない VM では
KVM_EXIT_ARM_NISVとして VMM へエグジット できる。保護 VM では、そのアクセスを VMM がエミュレートできないため、 代わりにゲストが同期外部アボートを受け取る。
デバッグ
保護ゲストはハードウェア支援デバッグを利用できない。MDSCR_EL1、
OSLAR_EL1、OSLSR_EL1、OSDLR_EL1、およびブレークポイント 0 と
ウォッチポイント 0 は RAZ/WI である。その他のデバッグレジスタへの
アクセスは未定義命令例外になる。
参考資料
Quentin Perret 氏による KVM Forum 2022 の講演「Protected KVM on arm64: A technical deep dive」は、その後いくつかの細部が変わっているものの、 pKVM についてさらに学ぶための良い資料であり続けている。