BitVisor Advent Calender 2018 の 19日目の記事です.
カレンダー主催者なのに投稿間に合ってなくてすみません,仮眠したら寝坊しました...
(20日目の方が先に投稿されてる...)
はじめに
最近,本家 BitVisor にも Intel CPU 向け Unsafe Nested Visortualization がサポートされるようになったみたいです.
そこで,今回はコードレベルでの処理流れについて,簡単にご説明します.
Nested 状態になってしまうとどうしても処理が複雑になり,それに伴いコードパスも複雑になってしまい,コードを追っかけようとするとすぐに混乱してしまいます... そのようなときにこの内容がお役に立てば幸いです.
なお,下記の内容は昨年の BitVisor Summit 6 時点での実装を思い出しながら書いているため,その後の修正や記憶違いによって間違っている箇所があるかもしれません,お気づきの点がございましたらご指摘ください.)
BitVisor の Unsafe Nested Virtualization って?
端的に言うと,BitVisor の上で Intel VT や AMD-V を使った VMM (KVM など) が動作させられる!(ただしセキュリティ的にはちょっと問題あるときもあるよ) という機能です.
具体的には以下のスライドや記事を見ていただくとよいです.
Unsafe Nested Virtualization (BitVisor Summit 4,株式会社イーゲル 榮樂様)
https://www.bitvisor.org/summit4/slides/bitvisor-summit-4-2-eiraku.pdf
Unsafe Nested Virtualization on Intel CPU (BitVisor Summit 6)
https://www.slideshare.net/DeepTokikane/unsafe-nested-virtualization-on-intel-cpu-83409391
Unsafe Nested Virtualization on Intel CPU (の捕捉)
https://qiita.com/deep_tkkn/items/0025e0012c07c57fdaed
BitVisor メインルーチンの実行パス (Nested じゃない場合)
Nested の実行パスを説明する前に,まずは BitVisor で OS を一つ動作させるときの実行パスを見たいと思います.
BitVisor のメインルーチンは(Intel CPU の場合) core/vt_main.c 内の vt_mainloop ()
という関数になります.
vt_mainloop ()
: https://bitbucket.org/bitvisor/bitvisor/src/f8d6f8f0751cd759f3c2f1b6f4c75b9180444c50/core/vt_main.c#vt_main.c-1236
vt_mainloop ()
の中にはいろいろ書いていますが,めちゃくちゃ単純に書くと
- ゲスト OS に処理切り替え (VMEntry),VMExit で処理が BitVisor に戻ってくる
- VMExit の原因に応じた処理
- その他いろいろチェックや次回 VMEntry の準備
という処理を繰り返すようになっています.
また,2. の処理は, vt__exit_reason ()
という関数で行います.
vt__exit_reason ()
: https://bitbucket.org/bitvisor/bitvisor/src/f8d6f8f0751cd759f3c2f1b6f4c75b9180444c50/core/vt_main.c#vt_main.c-1042
vt__exit_reason ()
の中身は VMCS の VM exit reason フィールド (VM exit の原因を示す番号が入っている) の値などに応じた処理を行うような swtich 文があります.
この流れを書いたのが以下の図になります(iPad pro & apple pencil で書いてみました,字が汚くてすみません...)
(図中,L0 をハードウェア直上で動く BitVisor, L1 を BitVisor 直上で動作する OS や VMM)
Nested な BitVisor の実行パス
では,Nested だとどうなるのでしょうか?
(以後,L0 をハードウェア直上で動く BitVisor, L1 を BitVisor 直上で動作する OS や VMM, L2 を L1 VMM 上で動作するゲスト OS という意味で用います)
Nested の場合, L2 OS を動かすための処理は前章で述べた 2. VMExit の原因に応じた処理 の一部として行います.
L1 から L2 に処理を切り替える == L1-->L2 への VMEntry (vmlunch/vmresume 命令の実行) です.このため,これらの命令実行時に発生する VMExit (L1 --> L0) に対応する exitreason() の処理として L2 を動作させています.
要するに,vmlunch/vmresume で VM exit した際のハンドラとして,L2 を動作させる処理は記述されています.
このハンドラの呼び出しはコードで言うと以下になります.
https://bitbucket.org/bitvisor/bitvisor/src/f8d6f8f0751cd759f3c2f1b6f4c75b9180444c50/core/vt_main.c#vt_main.c-1131
ちなみに,ここから実際に L2 VM を動作させる run_l2vm ()
までの呼び出しのパスは以下の通りです.
vt__exit_reason ()
--> do_vmlaunch ()
--> emul_vmlaunch ()
--> run_l2vm ()
https://bitbucket.org/bitvisor/bitvisor/src/f8d6f8f0751cd759f3c2f1b6f4c75b9180444c50/core/vt_main.c#vt_main.c-1028
-->https://bitbucket.org/bitvisor/bitvisor/src/f8d6f8f0751cd759f3c2f1b6f4c75b9180444c50/core/vt_shadow_vt.c#vt_shadow_vt.c-1374
-->https://bitbucket.org/bitvisor/bitvisor/src/f8d6f8f0751cd759f3c2f1b6f4c75b9180444c50/core/vt_shadow_vt.c#vt_shadow_vt.c-1250
これを図示したものが以下になります (また手書きです,すみません)
Unsafe nested virtualization での注意点
上記のような処理流れであるため,以下に注意が必要です.
- L1 で VMExit が起きた場合と L2 で VMExit が起きた場合では,BitVisor へのエントリーポイントや VM exit 後の実行パスが異なります.
- L2 で起きた VMExit は基本的に L0 では全くハンドリングしません,全て L1 へ処理を委ねます.
- なので,L2 で dbgsh とか実行し,BitVisor 向けの VMCALL 命令が実行されても,L0 の BitVisor は反応しません.
コードを読んで Nested 処理を追う場合や,L2 からの VM exit の際に何か処理をはさみたい場合は注意が必要になると思います.
おわりに
Unsafe Nested Virtualization における処理流れを書いてみました.
理解の助けになれれば幸いです.
Advent Calendar 参加者へのお礼
記事の内容とは直接関係ないのですが,この場でお礼をさせていただきたいと思ったので書きます.
今年の BitVisor Advent Calendar では僕はほとんど記事を投稿できていないのですが皆さんのご協力で(特に @hdk_2 さんと @mmi さん) 続けられております,本当にありがとうございます.
あと6日(既に20日目の記事が投稿されているのを観測しましたのであと5日) ですので今年も完走させていただければと思います.