Man page of CHIKU_WAIT(2)

システムソフトウェアとポエムの差が激しいので,高山病に注意

VBHを触ってみた

はじめに

この記事は,FUN part2 Advent Calendar 2019 - Adventarの19日目の記事です.

昨日の記事は,在学中にパートナーに振られたときどうするか - 草稿集でした.

VBHとは

github.com

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のイベントが発生した際にコールバックする簡単なサンプルです.

github.com

#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のカーネルモジュールをロードさせます. その後,このサンプル自体をビルドし,サンプルのカーネルモジュールをロードさせることで動作します. f:id:chikuwa_it:20191219223702p:plain dmesgで確認してみると,確かにコールバックされていることが確認できましたね.

おわりに

研究で関連で偶然名前を見かけたので試してみました.本当は,EPT Violationとかを起こして色々遊んでみようと思ったんですが,時間がなくてVMCALLだけのしょっぱいサンプルになってしまいました. ただ,良い感じにAPIが生えていて,カーネルモジュールとしてコードを記述できるのは便利ですね.セマンティックギャップを超えやすくていいなあ...と感じました.(卒業研究でセマンティックギャップに苦しんでいる)

明日のFUN part2 Advent Calendar 2019の記事は, kmdkukさんの「 Cコンパイラをセルフホストした話(締め切り駆動)|間に合わなかったらテック系ポッドキャストのススメ」です.