これはBitVisor Advent Calendar 2018の18日目の記事です.
こんにちは,@morimolymolyです.
今日はBitVisorのEPTを使ってカーネル空間の関数をトラップしようとしてできない,というような記事を書きます.
どんな記事を書くといいの? って思う人は例えばこんな記事とかどうでしょう?
- BitVisor 初心者体験談
... - ○○やりたいんだがよくわからん助けて
ということでわからないので助けてください!!(メーリスでやれ?)
EPTとは
EPTとは物理メモリ仮想化のためのVT-xで用意されている機構です.ゲスト物理アドレス(GPA)をホスト物理アドレス(HPA)に変換する際に使用します.
EPTを有効にするにはいくつかの段階を踏む必要があります.
MSR VPID AND EPT CAPABILITIES
まずはMSRのIA32_VMX_EPT_VPID_CAP MSR
を読んでプロセッサが対応している機能などを確認します.
BitVisorのソースではcore/vt_init.c
のvpid_init
とept_init
関数に実装があります.
static void
vpid_init (void)
{
u64 ept_vpid_cap;
asm_rdmsr64 (MSR_IA32_VMX_EPT_VPID_CAP, &ept_vpid_cap);
if (!(ept_vpid_cap & MSR_IA32_VMX_EPT_VPID_CAP_INVVPID_BIT))
return;
if (!(ept_vpid_cap &
MSR_IA32_VMX_EPT_VPID_CAP_INVVPID_SINGLE_CONTEXT_BIT))
return;
current->u.vt.vpid = 1; /* FIXME: VPID 1 only */
}
static void
ept_init (void)
{
u64 ept_vpid_cap;
asm_rdmsr64 (MSR_IA32_VMX_EPT_VPID_CAP, &ept_vpid_cap);
if (!(ept_vpid_cap & MSR_IA32_VMX_EPT_VPID_CAP_PAGEWALK_LENGTH_4_BIT))
return;
if (!(ept_vpid_cap & MSR_IA32_VMX_EPT_VPID_CAP_EPTSTRUCT_WB_BIT))
return;
current->u.vt.ept_available = true;
if (!(ept_vpid_cap & MSR_IA32_VMX_EPT_VPID_CAP_INVEPT_BIT))
return;
if (!(ept_vpid_cap & MSR_IA32_VMX_EPT_VPID_CAP_INVEPT_ALL_CONTEXT_BIT))
return;
current->u.vt.invept_available = true;
}
EPTの設定
vt_vminit
(core/vt_init.c) -> vt_paging_init
(core/vt_paging.c) -> vt_ept_init
(core/vt_ept.c)という流れでEPTの初期化関数が呼ばれます.
void
vt_ept_init (void)
{
struct vt_ept *ept;
int i;
ept = alloc (sizeof *ept);
alloc_page (&ept->ncr3tbl, &ept->ncr3tbl_phys);
memset (ept->ncr3tbl, 0, PAGESIZE);
ept->cleared = 1;
for (i = 0; i < NUM_OF_EPTBL; i++)
alloc_page (&ept->tbl[i], &ept->tbl_phys[i]);
ept->cnt = 0;
ept->cur.level = EPT_LEVELS;
current->u.vt.ept = ept;
asm_vmwrite64 (VMCS_EPT_POINTER, ept->ncr3tbl_phys |
VMCS_EPT_POINTER_EPT_WB | VMCS_EPT_PAGEWALK_LENGTH_4);
}
tbl
がEPTのエントリで,cur
でエントリを走査していきます.
EPTPにEPTのテーブルの物理アドレスをセットして設定は完了です.
BitVisorでのEPTの扱い
ゲストでEPT Violation(EPTエントリがマップされていなかったり,アクセス制限に引っかかるようなアクセスをシた場合におこる)が起こった場合,do_ept_violation
(core/vt_main.c)が呼ばれます.
static void
do_ept_violation (void)
{
ulong eqe;
u64 gp;
asm_vmread (VMCS_EXIT_QUALIFICATION, &eqe);
asm_vmread64 (VMCS_GUEST_PHYSICAL_ADDRESS, &gp);
vt_paging_npf (!!(eqe & EPT_VIOLATION_EXIT_QUAL_WRITE_BIT), gp);
}
Exit QUalificationとEPT Violationが発生したゲスト物理アドレスを取り出して,vt_paging_npf
を呼びます.
void
vt_paging_npf (bool write, u64 gphys)
{
#ifdef CPU_MMU_SPT_DISABLE
if (current->u.vt.vr.pg)
panic ("EPT violation while spt disabled");
#endif
if (ept_enabled ())
vt_ept_violation (write, gphys);
else
panic ("EPT violation while ept disabled");
}
これはSPT(Shadow Paging Table)とEPTとを抽象化するレイヤです.SPTはEPTが導入される前に用いられていたメモリ仮想化のテクニックです.今回はSPTではなくept_enabled
なのでvt_ept_violation
を呼び出します.
void
vt_ept_violation (bool write, u64 gphys)
{
struct vt_ept *ept;
ept = current->u.vt.ept;
mmio_lock ();
if (vt_ept_level (ept, gphys) > 0 &&
!mmio_range (gphys & ~PAGESIZE2M_MASK, PAGESIZE2M) &&
!vt_ept_map_2mpage (ept, gphys))
;
else if (!mmio_access_page (gphys, true))
vt_ept_map_page (ept, write, gphys); // こんかいはここ
mmio_unlock ();
}
MMIOのトラップはここで処理をしているみたいですが,今回はvt_ept_map_page
にいきます.
vt_ept_map_page
からマップを行うvt_ept_map_page_sub
を呼び出します.
static void
vt_ept_map_page_sub (struct vt_ept *ept, bool write, u64 gphys)
{
bool fakerom;
u64 hphys;
u32 hattr;
u64 *p;
cur_move (ept, gphys);
p = cur_fill (ept, gphys, 0);
hphys = current->gmm.gp2hp (gphys, &fakerom) & ~PAGESIZE_MASK;
if (fakerom && write)
panic ("EPT: Writing to VMM memory.");
hattr = (cache_get_gmtrr_type (gphys) << EPTE_MT_SHIFT) |
EPTE_READEXEC | EPTE_WRITE;
if (fakerom)
hattr &= ~EPTE_WRITE;
*p = hphys | hattr;
}
cur_move
で現在のEPTエントリを更新,cur_fill
でPTEを取得.(この中でPML4E,PDPTE,PDEの設定もしている)
hphys = current->gmm.gp2hp (gphys, &fakerom) & ~PAGESIZE_MASK;
でフラグのぶん11bitマスク.あとは適当にフラグを設定してPTEに書き込むだけです.
TLB Shootdown
もし,任意のアドレスでEPT Violationを起こしたい場合は,vt_ept_map_page_sub
でフラグを落とした設定をすればよい.ここで気をつけなければいけないのはTLBである.TLBにはEPTのキャッシュも存在していて,これはコアごとに存在する.つまり一度フラッシュしておかなければ,別のコアでは古いキャッシュ情報が使用されてしまう恐れがある.ここでTLB Shootdownというテクニックを用いる.
TLB Shootdownを行うにはこの記事が参考になる.
実装
さて,実際にTLB Shootdownとトラップする機構を実装してみた.
レポジトリ
具体的には,GPAとGVAでトラップできるようにVMCALLを追加した.
VMCALLの実装は
core/vmcall.c
core/vmcall3.c
で,VMCALLをするためのLKMは
vmcall/
以下に実装した.
今回はcommit_creds
をトラップすることにして,vmcall/cc
にこれを呼び出すLKMを書いた.
static void
do_ept_violation (void)
{
ulong eqe;
u64 gp, va;
ulong rax;
current->vmctl.read_general_reg(GENERAL_REG_RAX, &rax);
asm_vmread (VMCS_EXIT_QUALIFICATION, &eqe);
asm_vmread64 (VMCS_GUEST_PHYSICAL_ADDRESS, &gp);
asm_vmread64 (VMCS_GUEST_LINEAR_ADDR, &va);
if(is_trapped(gp) || is_trapped_gva(va)){
vt_paging_npf (!!(eqe & EPT_VIOLATION_EXIT_QUAL_WRITE_BIT), gp, false, true);
}else{
vt_paging_npf (!!(eqe & EPT_VIOLATION_EXIT_QUAL_WRITE_BIT), gp, false, false);
}
}
GPAがトラップするアドレスならis_trapped
,GVAがトラップされるならis_trapped_gva
からtrueが帰ってくる.vt_paging_npf
にはそれぞれtrapとexecという引数を追加して,trapする場合はPTEに実行フラグをexecの内容で書き込む.今回はトラップされるアドレスなら実行フラグが問答無用でオフになるので,該当するアドレス空間が参照された段階で無限EPT Violationループに陥る.
static void
vt_ept_map_page_sub (struct vt_ept *ept, bool write, u64 gphys, bool execute, bool trap)
{
bool fakerom;
u64 hphys;
u32 hattr;
u64 *p;
cur_move (ept, gphys);
p = cur_fill (ept, gphys, 0);
// フラグのために11 bit maskしている
hphys = current->gmm.gp2hp (gphys, &fakerom) & ~PAGESIZE_MASK;
if (fakerom && write)
panic ("EPT: Writing to VMM memory.");
if(trap){
if(execute){
hattr = (cache_get_gmtrr_type (gphys) << EPTE_MT_SHIFT) |
EPTE_READ | EPTE_WRITE | EPTE_EXEC;
}else{
hattr = (cache_get_gmtrr_type (gphys) << EPTE_MT_SHIFT) |
EPTE_READ | EPTE_WRITE;
printf("TV: Removed wrx from 0x%llx\n", gphys);
printf("TV: Host address is 0x%llx\n", (u64)current->gmm.gp2hp (gphys, &fakerom));
printf("PTE: 0x%llx\n", hphys | hattr);
}
}else{
hattr = (cache_get_gmtrr_type (gphys) << EPTE_MT_SHIFT) |
EPTE_READEXEC | EPTE_WRITE;
}
if (fakerom)
hattr &= ~EPTE_WRITE;
*p = hphys | hattr;
}
問題点
というわけでVMCALLを使ってカーネル空間の関数をKASLR無効でいくつかトラップしてみたが,EPT Violationが発生しなかった.TLB Shootdownはエントリが追加される前に走っているのは確認できた.
ログはgistにアップロードした.
検証方法
vmcallのLKM内のトラップするアドレスを書き換えたあと,make && make test
で実行する.
そうすると実行フラグを落としたはずの関数が実行されてしまうことがわかる.
たすけて…
HyperVillageのslackか当記事コメントでどうかよろしくおねがいします
解決したら全部バッチリまとめた記事を書きます……
参考
https://qiita.com/RKX1209/items/38c8403a73c91bd782bc
https://www.slideshare.net/DeepTokikane/ept-tlb
https://qiita.com/deep_tkkn/items/0220df8d569b5a6a8bc3