LoginSignup
1
0

More than 3 years have passed since last update.

BitVisorのNMI処理の競合対策

Posted at

ご存知の通りBitVisorのNMI処理には以下のパターンがあります:

  • 仮想マシン上のプログラムを実行中のNMI - #VMEXITで処理
  • ハイパーバイザーを実行中のVMI - NMIハンドラーで処理

いずれにしても、VMM側のpanicの場合を除き、仮想マシン上のオペレーティングシステムのNMIハンドラーが呼び出される形になります。

それで、#VMEXITであればすぐにNMIをinjectすればいいし、NMIハンドラーが実行された場合は内部でカウンターをインクリメントしておき、適切なタイミングでinjectすればいいのですが、特にvmlaunch/vmresume/vmrun命令の周辺では、際どいタイミングというものが存在するわけです。それをどう対策しているのか簡単に紹介します。

Intelプロセッサ用

Intelプロセッサの場合NMIをマスクすることができません。そのためvmlaunch/vmresume命令の直前までNMIが発生する可能性があります。それを逃さず処理する仕掛けがあります。

その仕掛けを設定している部分は、core/asm.sの中を%gs:gs_nmi_criticalというキーワードで検索していただくと見つかります。vmlaunch/vmresume命令を使うより前にここにアドレスを設定し、後で0を設定しています。このアドレスに書き込まれている3つのアドレスは、順に、際どい部分の開始アドレス、際どい部分の終了アドレス(範囲の次のバイト)と、際どい部分を実行中にNMIが発生した場合の戻り先のアドレスを表します。この範囲内を戻り先アドレスとするNMIが発生したら、戻り先アドレスを書き換えて、残りの部分は強制的にすっ飛ばしてしまう仕掛けです。

そして、この範囲内で%gs:gs_nmi_countをチェックします。これは通常のNMIの際にインクリメントされます。範囲外でNMIが発生して、そのままここに来た場合はこのチェックに引っかかり、NMI用の脱出口にジャンプするのでvmlaunch/vmresume命令は実行されません。範囲内でNMIが発生した場合はNMIハンドラーから戻り先のアドレスが変更され、強制的に同じ脱出口にジャンプします。

なぜこのような範囲を設定しているかというと、vmlaunch/vmresume命令を実行する直前までは、すっ飛ばして欲しいけれど、実行した後は、そのVM Exitを処理する必要があって、すっ飛ばしてほしくないためです。NMIがマスクできない以上、際どい部分はそのようにNMIハンドラー側で何とかする必要がありました。

NMIハンドラーはcore/nmi_handler.sにあります。戻り先のアドレスを見て差し替える処理があります。

AMDプロセッサ用

AMDの場合はSVMの機能によりNMIをマスクできますのでもう少し簡単です。core/asm.sの中を見ていただくと、vmrun命令より手前にclgiという命令があります。これが、NMIを含めて割り込み禁止にする命令です。(sti命令でセットされる割り込み許可フラグよりも優先されるので、clgiを実行した状態では外部割り込みもすべて発生しません。) この状態で%gs:gs_nmi_countをチェックすれば、ここからvmrun命令までにNMIが発生して値が変化することはありません。

vmrun命令はclgiのまま実行します。仮想マシン上に実行が移った時には自動的にstgiを実行したのと同じ状態になっており、NMIも受け付けられます。clgi命令実行後にNMI信号が入っていた場合、そのNMIはvmrun命令の直後に受け付けられ、即座に#VMEXITとなります。

また、#VMEXIT後はclgiの状態になるので、必要なレジスターの切り替え処理後にstgi命令を実行してNMIを受け付けられるようにしています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0