BitVisor の IOMMU の話

# はじめに
ちょうど一週間前に，YsuOS さんの記事で TinyVisor (BitVisor の派生ハイパバイザ，なぜか VMが2つ動く) での IOMMU の使われ方が解説されました．

TinyVisorのIOMMUのコードを読んでみたよ - Qiita - https://qiita.com/YsuOS/items/a6deab2e92fb77be7006

TinyVisor は仮想化なしで 2 つの VM を動かすために，IOMMU を使っていました．

しかし実は，VM が 1 つしか動かない BitVisor にも IOMMU を用いた機能があります．
BitVisor では，デバイスの DMA から BitVisor 自身守るために IOMMU を使います．今日はこれについて書いてみます．

# IOMMU って何?
デバイスが DMA するときにもページングっぽいことをできるようにするための機能です．
PCI-passthrough などするときに物理アドレスが DMA する先を VM の物理メモリに対応させるために使ったり使ってなかったりしたりしなかったりするみたいですよ?

詳しくは，以下のページが分かりやすいと思いますので，そちらを読んでみてください．

ハイパーバイザの作り方～ちゃんと理解する仮想化技術～ 第１６回 PCIパススルーその２「VT-dの詳細」 - https://syuu1228.github.io/howto_implement_hypervisor/part16.html



# IOMMU で BitVisor を守るってどういうこと?
BitVisor が正しく動作し続けるためには，ゲスト OS が BitVisor のメモリ領域を読み出したり逆に書き込んだりできないようにする必要があります．
ゲスト OS から CPU を介したメモリアクセスについては，Nested Paging の機構で止めています．

しかし，ゲスト OS がへんてこりんな設定をした PCI デバイスがあると，DMA で BitVisor のメモリ領域を読みだされたり逆に書き込まれたりする可能性があります．

これを防ぐために，BitVisor は IOMMU を使います．
ページング機構と同様，IOMMU でもマッピングがないメモリ領域にはアクセスできないため，BitVisor のメモリ領域をマッピングしないことで BitVisor を守ることができます．
ですので，セキュリティ用途では有効にしておいた方が良さそうな機能です．

BitVisor とゲスト OS とでデバイスを共有している場合は，色々面倒なことをしていそうなんですが，それは説明も面倒そうなので，BitVisor の論文読んでください．
(DMA で使うバッファーのシャドーイングとかしてるっぽいです)

BitVisor: a thin hypervisor for enforcing i/o device security - https://dl.acm.org/citation.cfm?id=1508311

ACM のアカウントとかない人はこっちから読めるかも? https://www.os.ecc.u-tokyo.ac.jp/papers/vee2009-shina.pdf

# 機能の使い方
make config と叩いて
 `VTD_TRANS            Enable VT-d translation`
というのを有効にします．
名前から察するに，Intel VT-d を使うものだと思います．
そういえば，BitVisor は AMD の IOMMU に対応しているんでしょうか?


# 動かないんだけど...
具体的には，Linux の起動途中に何らかの PCI デバイスが動いてなさそうな感じのログが出て止まってしまったりします．
(僕の場合は NVMe でした)

そんなときはパッチを当てましょう．
以下のパッチで動くはずです．
(今の最新 BitVisor に当てて試したことないので，当たらなかったり動かなかったしたらごめんなさい)
(さっさと本家にパッチ投げないとですね...)

```diff
diff --git a/drivers/iommu.c b/drivers/iommu.c
--- a/drivers/iommu.c
+++ b/drivers/iommu.c
@@ -652,8 +652,10 @@
 #ifdef VTD_TRANS
 	
 	struct acpi_drhd_u *drhd;
-	unsigned long i;
+	unsigned long i, n = 0;
 	int remap, dom, ndom, f;
+	u64 base, len, end_phys = 0;
+	u32 type;
 	
 	if (!iommu_detected)
 		return;
@@ -667,9 +669,14 @@
 #endif // of VTD_DEBUG
 	
 	ndom=remap_preconf();
+
+	do {
+		n = getsysmemmap (n, &base, &len, &type);
+		end_phys = (end_phys < base + len) ? (base + len - 1) : end_phys;
+	} while (n != 0);
 	
 	printf("(IOMMU) dom 0(PT Devs.) ");
-	for (i = 0; i <= 0xfffff; i++) 
+	for (i = 0; i <= (end_phys >> 12); i++) 
 		dmar_map_page(dom_io[0], i, ((i>=vmm_start_inf() >> 12 && i<vmm_term_inf() >> 12) ? PERM_DMA_NO : PERM_DMA_RW));
 	for (dom=1; dom<ndom ; dom++) {
 		printf("%x",dom);
@@ -678,7 +685,7 @@
 			printf("(%x:%x:%x) ", rem[i].bus, rem[i].df.dev_no, rem[i].df.func_no);
 			break;
 		}
-		for (i = 0; i <= 0xfffff; i++) { 
+		for (i = 0; i <= (end_phys >> 12); i++) { 
 			f=0;
 			for (remap=0; remap<num_remap ; remap++) {
 				if (rem[remap].dom==dom && i>=rem[remap].phys && i<rem[remap].phys+rem[remap].num_pages) {
```

上記のコードでは，元々 BitVisor が 4GB までしか IOMMU のマッピングを作っていませんでした　(0xfffff は 4GB 最後のページフレーム番号です)．
これだと，PCI デバイスが 4GB 以降のアドレスに DMA しようとしたときに動かなくなります．
そこで，このパッチは，BIOS が返す物理アドレスの終端までのマッピングを作るようにしています(しているつもりです)．

# おわりに
というわけで，BitVisor での IOMMU のお話でした．

実際にゲスト OS で怪しいデバドラを作って BitVisor を壊せるか否かというところまで試せばよかったんですが，今回はそこまではやってません．