feature image

2024年12月22日 | ブログ記事

BitVisorのブートローダを作ってみた

この記事はアドベントカレンダー2024の記事です。

はじめに

こんばんは、ほたるいかといいます。
x86のハイパーバイザの仕組みに興味を持ってBitVisorと呼ばれるハイパーバイザを調べていたついでに第一段階のブートローダをRustで作った(レポジトリ)のでその仕組みや詰まったところ等を記事にしようと思います。時間が足りなくて全部は作れなかった
BitVisorについてここ数ヶ月調べた程度であり、間違っているところ等ありましたら教えていただけると助かります。

BitVisorについて

BitVisorとは、thin hypervisor と呼ばれるハイパーバイザの一種でありOSを一台しか動かせない変わりに軽い動作を実現したハイパーバイザです。
BitVisorはOSの更に上位でファイルの暗号化やデバイスの隠蔽をすることでセキュリティの向上を主目的としているようです。

第一段階ブートローダについて

今回はx86のuefi環境の話をします。他にもBIOS環境やaarch64アーキテクチャ等にも対応しているようです。
BitVisorは2段階ブートローダを採用しており、1段階目のブートローダでbitvisor.elfの先頭から0x10000までの領域にある2段階目のブートローダをメモリ上に読み出し、2段階目のブートローダでBitVisor全体を読み出す形式になっています。BIOS環境とコードを共通化するためにこのような仕様になっていそうです。
1段階目のブートローダは/boot/uefi-loader/にあります。
以下では、実装しているときに知った面白い仕様について説明していきます。

ファームウェアドライバ切断

BitVisorはuefiから呼び出されるとcpuの仮想化を行ってuefiに制御を返し、その後にuefiからOSを呼び出すという挙動をするため、完全な仮想化が行われているハイパーバイザと比較してOSが実デバイスを直接制御する仕組みとなっています。BitVisorがネットワークのログを取る等でデバイスを操作することが必要になった場合、para passthrough driverと呼ばれるデータの監視や変換を行うドライバを利用するわけですが、このときにuefiが提供しているドライバと競合する恐れがあります。そのため、BitVisor起動時にuefiのUninstallMultipleProtocolInterfacesと呼ばれる関数により、一旦すべてのデバイスドライバを切断しています。

Apple製品のバグへの対応

ファームウェアドライバを切断するときに利用するUninstallMultipleProtocolInterfaces関数ですが、Apple Mac mini 2018では実装が間違っているせいで切断できなくなっています。そのため、BlockIoCryptoProtocolというプロトコルがあるかどうかでこの製品かどうかを判別して、Mac mini 2018モデルであればこのバグを回避した関数を代わりに利用しています。

ファームウェアドライバインストール時の対応

これはどこにも詳しい記述がなかったので、もしかしたら間違っているかもしれません。(参考)
BitVisorはPCのディープスリープ(S2以上)からの復帰時にCPUのコンテクストを復元するためスリープ検出のフックを作成したりするためにACPIを改変しています。この修正がファームウェアドライバ(EFI_ACPI_TABLE_PROTOCOL)の再インストールにより失われないように、uefiのInstallConfigurationTable関数が呼び出されたときにフックされるbsdriverというドライバを作成し、ACPIに関連するファームウェアのインストールが行われたときに改変しています。

実際の実装で詰まったところ

実際にRustで1段階目のブートローダの実装をしてみました。基本的にMilvusVisorという、BitVisorのaarch64アーキテクチャバージョンであるハイパーバイザがRustで書かれているため、そのコードを改変する形で作成しました。

BitVisorのelfが32bit

bitvisor.elfを読み出すときにエラーが発生していたのですが、これはelfが32bitであったからでした。これは、BIOSの32bitと互換性をもたせるためなのでしょうか?

EFI_FILE_PROTOCOL

bitvisor.elfを起動するには、bitvisor.elfに対応するハンドルをOpenして、メモリ上に確保した領域にそれをRead関数で展開すればよい。EFI_FILE_PROTOCOL.Read()は読んだ分それに対応するプロトコルのハンドルのポインタが増加してしまうため、elfファイルを読むなどしたあとはEFI_FILE_PROTOCOL.seek()で戻る必要がある。また、EFI_FILE_PROTOCOL.seek()はプロトコルの初期位置からの絶対アドレスで示される点に注意です。(1敗)

終わりに

今回はBitVisorのブートローダをRustで書き直すという誰得なことをしてみたわけですが、かけられる時間があまりなかったためbsdriverは既存のコードをそのまま利用していました。Rustでuefi driverを書いている先例も見つけたので、暇なときに書いてみたいです。

明日は、zoi_dayoさん 、 nzt3さん、 cp20さんの3名です! お楽しみに!!!

参考情報

BitVisorのブートシーケンス解説 (UEFI編)
BitVisorのUEFI対応
BitVisor 2017年の主な変更点
BitVisor 2019年の主な変更点

hotaru icon
この記事を書いた人
hotaru

英語がすらすら読めるようになりたい

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2024年12月24日
クリスマスを充実して過ごすためのたった一つの方法
Naru820 icon Naru820
2024年12月11日
Nixで実行環境のライセンス違反を予防する話
comavius icon comavius
2024年12月11日
最近参加したオフラインイベントについてまとめてみる
Pugma icon Pugma
2024年12月2日
【中国民族音楽作曲技法】①五声音階を認識しましょう
Natsuki icon Natsuki
2024年12月23日
ICPC 2024 Asia Yokohama Regional 参加記(Nzt3 視点)
Nzt3 icon Nzt3
2024年12月19日
Borgo-langを使ってみたかった
comavius icon comavius
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記