VBHを触ってみた
はじめに
この記事は,FUN part2 Advent Calendar 2019 - Adventarの19日目の記事です.
昨日の記事は,在学中にパートナーに振られたときどうするか - 草稿集でした.
VBHとは
VBHはIntelのハードウェア仮想化支援機能(VT)を活用したThin-Hypervisorの一種です. Linuxカーネルにカーネルモジュールとしてロードすることで,仮想化できる仕組みをしています. Thin-Hypervisorであるため,基本はI/Oパススルーであり,仮想化によるオーバヘッドを最小限に留めています.
VBHは主にVMI(Virtual Machine Introspection)を実現するためのハイパーバイザです. VMIとは,仮想マシンで実行されているソフトウェアを外部(ハイパーバイザ)から分析・解析して保護することです. 従来のVMIでは,セマンティックギャップの解消が大きな課題でした.この文脈でのセマンティックギャップとは,仮想マシン内部のリソースに関する情報をハイパーバイザ側で得られる情報から再構築できないことです. 例えば,複数のプロセスからファイルの書き込みなどのI/Oを発行しても,ハイパーバイザではOSからのI/Oとしか判断できないといった感じです.
そこでVBHは,Linuxカーネルのモジュールとしてロードすることで,Linuxカーネル側で取得できる情報によってセマンティックギャップを埋め,仮想化機能を活用した保護を可能にしています. どのような保護かというと,EPT(Extended Page Table)を活用したメモリの提供によって,スタックやヒープを悪意のある操作から保護やコードや読み取り専用ページなどを書き込みから保護があります. また,VTを活用することで,MSRを悪意のある書き込みから保護や悪意のある変更からCRを保護が可能です.
しかし,あくまでもVBHはベアメタルカーネルを保護するためのツールを提供するもので,VBH単体ではカーネルの保護は提供しません. 代わりに,ハイパーバイザを参照・制御するためのAPIを提供します. そのため,保護を実現するためにはこれらのAPIを活用して自分でコードを書く必要があります.
使い方
提供するAPI群
APIは合計で20個ほどしかありません. 代表的なAPIとしては,
hvi_physmem_map_to_host:ゲスト物理メモリをマップ hvi_physmem_unmap:ゲスト物理メモリをアンマップ hvi_get_ept_page_protection:ページテーブルのアクセス権を確認 hvi_set_ept_page_protection:ページテーブルのアクセス権を変更 hvi_register_event_callback:イベントのコールバック登録 int hvi_unregister_event_callback:イベントのコールバック登録解除 int hvi_inject_trap:ゲストにトラップ挿入
コールバックできるイベント
APIの中に,特定のイベントが発生した際にコールバックできる機能があります. コールバックできるイベントは,以下の通りです.
EPT Violation MSR/CR Write XSETBV/XCR modification Exception Vmcall MTF exit Max event
サンプル
このサンプルでは,VMCALLのイベントが発生した際にコールバックする簡単なサンプルです.
#include <linux/module.h> #include "hypervisor_introspection.h" #include "vmx_common.h" int handle_vmcall(hv_event_e type, unsigned char *data, int size, int *allow) { if (type != vmcall) { pr_err("Invalid event(vmmcall) %d\n", type); return -EINVAL; } pr_info("Handled vmcall!\n"); return 0; } static int __init vbh_sample_init(void) { struct hvi_event_callback vmcall_event; int status; vmcall_event.event = vmcall; vmcall_event.callback = handle_vmcall; status = hvi_register_event_callback(&vmcall_event, 1); if (status){ pr_err(" failed hvi_register_event_callback, status : %d\n",status); } asm_make_vmcall(1, NULL); return 0; } static void __exit vbh_sample_uninit(void) { asm_make_vmcall(1, NULL); hvi_unregister_event_callback(vmcall); } module_init(vbh_sample_init); module_exit(vbh_sample_uninit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yuki Nakata");
カーネルモジュールの初期化関数では,hvi_register_event_callback関数を使って,VMCALLが発生した際のコールバックを登録します. 第1引数には,hvi_event_callbackという構造体のポインタを入れ,第2引数には登録するコールバックの数を登録します. hvi_event_callbackのeventメンバには,ハンドルするイベントの種類,callbackメンバには,コールバックで呼び出す関数を入れます.
hvi_register_event_callbackで登録したあと,確認のために,VMCALLを発行しています.
このサンプルを動作させるには,VBHをビルドし,VBHのカーネルモジュールをロードさせます. その後,このサンプル自体をビルドし,サンプルのカーネルモジュールをロードさせることで動作します. dmesgで確認してみると,確かにコールバックされていることが確認できましたね.
おわりに
研究で関連で偶然名前を見かけたので試してみました.本当は,EPT Violationとかを起こして色々遊んでみようと思ったんですが,時間がなくてVMCALLだけのしょっぱいサンプルになってしまいました. ただ,良い感じにAPIが生えていて,カーネルモジュールとしてコードを記述できるのは便利ですね.セマンティックギャップを超えやすくていいなあ...と感じました.(卒業研究でセマンティックギャップに苦しんでいる)
明日のFUN part2 Advent Calendar 2019の記事は, kmdkukさんの「 Cコンパイラをセルフホストした話(締め切り駆動)|間に合わなかったらテック系ポッドキャストのススメ」です.