Unikernel 用サンドボックス実行環境 Solo5

## はじめに
本記事では Unikernel をセキュアなサンドボックス実行環境で動作させるための [Solo5](https://github.com/Solo5/solo5) について、その背景と構成を中心に書いていきます。Solo5 ソースコードの深いところまでは追いません。これはまた別の機会に。

## Solo5 とは
[MirageOS](https://mirage.io/) や [IncludeOS](https://www.includeos.org/) など Unikernel ベースのアプリケーションをサンドボックス環境で動作させるためのソフトウェアです。主に下記の特徴を持ちます。
&nbsp;&nbsp;&nbsp;&nbsp;- Unikernel を動作させるための必要十分かつセキュアな環境の提供
&nbsp;&nbsp;&nbsp;&nbsp;- Unikernel と接する共通的な API の提供

Unikernel における発想の１つとして「アプリケーションサイズを小さくすれば外からの攻撃範囲も狭くなる」という考え方がありますが、Solo5 ではこの発想を Unikernel 実行環境にも適用しています。

幾つかの Unikernel 実装では Xen を実行環境として利用することが出来ます。しかし、シンプルな機能実装がウリの Unikernel にとって Xen が有している様々な機能を全て必要とすることは稀です。したがって Xen や QEMU 等の機能盛り沢山の仮想マシンレイヤ(+ハイパバイザ)は Unikernel 実行環境としてオーバースペックになるだけでなく、外から攻撃されうる範囲を意図せず大きくさせてしまいます。仮想マシンレイヤの脆弱性では[フロッピードライブ](https://jvndb.jvn.jp/ja/contents/2015/JVNDB-2015-002668.html)の例があったり、「要らない仮想デバイス機能を極力省いていこう」という発想は自然かと思います。

Solo5 の開発は [IBM Research](https://developer.ibm.com/open/projects/solo5-unikernel/) の方々によって始められ、現在では [Robur](https://robur.io/) のメンバや世界中のコントリビュータも参加してメンテや機能拡充が実施されています。ソースコードは [Github](https://github.com/Solo5/solo5) で公開されています。

[紹介ビデオ](https://developer.ibm.com/code/videos/solo5-unikernel-tech-talk/) や [FOSDEM2019発表スライド](https://fosdem.org/2019/schedule/event/solo5_unikernels/attachments/slides/3281/export/events/attachments/solo5_unikernels/slides/3281/solo5_fosdem19.pdf) があるので、見てみるとより理解が深まるかと思います。

[Blogの投稿](https://mirage.io/blog/introducing-solo5) から推測するに、Solo5 は最初 QEMU/KVM 上で Unikernel を動作させるためのフレームワークとして開発が始まったように思います (これは私の個人的見解ですが)。そこからは、よりセキュアにするための施策や多様なプラットフォームへの対応を進めてきているように見えます。

## Solo5 対応の Unikernel  
2020年4月現在、Solo5 を利用して動かすことが可能な Unikernel 実装は
&nbsp;&nbsp;&nbsp;&nbsp;- [MirageOS](https://mirage.io/)
&nbsp;&nbsp;&nbsp;&nbsp;- [IncludeOS](http://www.includeos.org/)
&nbsp;&nbsp;&nbsp;&nbsp;- [Rumprun](https://github.com/nabla-containers/rumprun) (これはどうやら本家 Rumprun ではなく、[Nabla containers](https://nabla-containers.github.io/) 用にforkしたものが対象らしい)
です。

## Solo5 が動作するプラットフォーム
2020年4月現在、
&nbsp;&nbsp;&nbsp;&nbsp;1. Linux with KVM
&nbsp;&nbsp;&nbsp;&nbsp;2. FreeBSD, OpenBSD with vmm
&nbsp;&nbsp;&nbsp;&nbsp;3. QEMU/KVM with virtio
&nbsp;&nbsp;&nbsp;&nbsp;4. [Genode](https://genode.org/)
&nbsp;&nbsp;&nbsp;&nbsp;5. [Muen](https://muen.codelabs.ch/)
の上で動作が確認されています。一般的な Linux もしくは BSD系OS の他に、Genode や Muen などの Microkernel(Separation kernel)上でも動作します。

Linux、FreeBSD もしくは OpenBSD を利用する場合には、Solo5 を利用する Unikernel アプリケーションを仮想CPU上、またはホストOS(=つまり物理CPU)の上で直接実行させることが可能です。

また、利用可能なハードウェア(アーキテクチャ)は64-bitの x86、ARM と PowerPC です。残念ながら32-bit環境では利用できません。

## 構成

Solo5 はサーバ仮想化環境を中心とする複数のプラットフォーム(例えば QEMU/KVM on Linux とか vmm on FreeBSD/OpenBSD)上で Unikernel アプリケーションを動作させるための抽象化レイヤの役目を果たします。下図は、 Linux をホストOSとして Solo5 を用いて MirageOS Unikernel を動作させるときの構成です。異なるプラットフォームに対しては、別々のプラットフォームレイヤが用意されています。例えば Linux で KVM(Kernel Virtual Machine) を用いて MirageOS Unikernel を動かす場合、仮想マシンレイヤとして hvt(hardware virtualized tender、Solo5とともに開発している小さい仮想マシン) と virtio(実際にはQEMUを使う) の2種類を使うことが出来ます。
![solo5_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291185/a3b81b45-aa66-a2e5-37c4-1a4c5a1659de.png)


仮想マシンを使っている多くの方は意外に思うかもしれませんが、virtio を利用する構成は<font color="Red">現在推奨されていません</font>。virtio 構成では QEMU を使う必要があるため、上記で述べたような「小さな仮想マシンレイヤ」を実現できない事が原因です。コミュニティとしてはフェードアウトの意向かつ機能アップデートは無しの方針なので、KVM を使いたい場合は hvt を利用するのが賢明です。

Solo5 の主な部分は C と アセンブリ で記述されています。「C なんかで書いてセキュリティ的に大丈夫かいな!?」と思うかもしれませんが、ASLR(Address Space Layout Randomization) や SSP(Stack Smashing Protection) などを積極的に採用して脆弱性に対しセンシティヴに実装されています。

ちなみに以前の Solo5 では1つの Unikernel に対して1つのネットワークデバイスしか使えなかったりと使い勝手がちょっとよくなかったのですが、機能拡張によってマルチデバイス対応も可能となりました。Solo5 はセキュア性、シンプルさ、そして機能拡張のバランスをとりつつ日々進化しています。

### プラットフォームレイヤ

プラットフォームレイヤとしては幾つかありますが、(私の独断と偏見で)利用しやすい hvt と spt について少し説明します。Geode と Muen のみなさんゴメンナサイm(_ _)m。

#### hvt (hardware virtualized tender)

hvt は仮想マシン環境を作り出し、Unikernel を動作させるための仮想マシンレイヤです。Linux の場合には KVM、FreeBSD/OpenBSD の場合には vmm を利用します。hvm 単体での主なお仕事は下記の通りです。

1. Unikernel バイナリ(ELFイメージ)のロード
2. 仮想マシン環境の初期化 (メモリ領域の確保、デバイスの準備、KVM や vmm の API を叩いたり)
3. 仮想マシンのスタート
4. ゲスト環境からの例外発生時(I/O発行とか)のトラップ処理
5. 仮想マシンのストップ

技術的な変遷では各種デバイス(ブロックデバイス、ネットワークデバイス、コンソールデバイス)のモジュールバイナリの扱い方が変わりました。以前は Unikernel が必要としない機能のバイナリモジュールをごっそり省くアプローチを採用していましたので、Unikernel のビルドとともにその都度 hvt バイナリをビルドしていました。この場合、 hvm バイナリは同じくビルドされた Unikernel 専用となってしまいます。しかしこれではどんな環境でも同じ hvm バイナリを使うことが出来なくなって不便なため、現在では hvm バイナリとしてはデバイスモジュールを全部入りにしています。とはいえこれではセキュリティ的に良くないので、Unikernel 側にアクセス可能なデバイスの情報を埋め込んでデバイスアクセス制限を可能とする仕組み(application manifest アプローチ)が実装されています。これについては [architecture.md](https://github.com/Solo5/solo5/blob/master/docs/architecture.md#application-manifest) に詳しい記載があります。

また、昔は hvt という名前ではありませんでした。実は Solo5 が Linux 上での QEMU/KVM をターゲットとして開発がスタートしたため、当初 ukvm という名前でした。後に FreeBSD/OpenBSD の vmm をターゲットにしたりと KVM 専従ではなくなったため、改名されています。

#### spt (sandboxed process tender)

spt は Linux の seccomp 機能を利用して Unikernel を動作させるためのソフトウェアです。仮想マシンを使わないため、Unikernel アプリケーションは Linux の1プロセスとして動作します。 hvt を利用する場合は仮想化のオーバヘッドが生じますが、この spt を利用する場合は仮想化オーバヘッドが生じません。spt 単体の主なお仕事は下記の通りです。

1. Unikernel バイナリ(ELFイメージ)のロード
2. Unikernel実行環境の設定 (メモリ領域の確保、デバイスの準備、seccomp の API を叩いたり)
3. プロセスのスタート

spt では seccomp を使うことで Unikernel 側から利用可能な Linux システムコールアクセス種類を最小限にし、ホスト Linux に対するセキュア性を向上させています。[spt のコード](https://github.com/Solo5/solo5/blob/master/tenders/spt/spt_core.c)を読むと、許可されているシステムコールの数は 64-bit ARM と PowerPC で 8つ、64-bit x86 で 9つ のようです。

spt もデバイスモジュールの作りについては hvt と同様に application manifest アプローチを採用しています。

### Unikernel側のインタフェース

Solo5 自体は純粋にサンドボックス環境のみなので、実際には Unikernel とのインタフェース部分として主に相手(Unikernel)側の言語で書かれたモジュール(要は Unikernel と Solo5 の結合部ですね)が必要となります。例えば、私が推している [MirageOS Unikernel](https://mirage.io/) では Solo5 に関連するパッケージとして下記があります。

```
imada@ubuntu:~$ opam list | grep solo5
mirage-block-solo5        0.6.1       Solo5 implementation of MirageOS block interface
mirage-bootvar-solo5      0.6.0       Solo5 implementation of MirageOS Bootvar interface
mirage-console-solo5      0.6.1       Solo5 implementation of MirageOS console interface
mirage-net-solo5          0.6.1       Solo5 implementation of MirageOS network interface
mirage-solo5              0.6.2       Solo5 core platform libraries for MirageOS
```

`mirage-solo5` パッケージはインタフェースのコア部分のモジュールを提供しており、その他の `mirage-***-solo5` パッケージではネットワークやコンソールなどデバイス関連のモジュール、起動時の引数を扱うモジュールが提供されています。そしてこれらのパッケージでは、 MirageOS Unikernel のメイン言語である OCaml と Solo5 のメイン言語である C のコードが混在しています。

## Nabla Containers

IBM Research の方が Solo5 と Unikernel を核とする [Nagla Containers](https://nabla-containers.github.io/) というプロジェクトを開始しています。これは MirageOS などの Unikernel を Solo5 with spt で動作させ、コンテナとして管理させるモノ(フレームワーク)です。当然ながらその売りは、「Strong isolation なコンテナ環境を提供する」ことにあります。

Solo5、アプリレイヤおよびコンテナとしての運用まで包括されたフレームワークまで提供することにより、ユーザが利用しやすくなるのではないかと思います。

また、公式ではありませんが Linux を Solo5 の上で動かそうとしている方がいるようです([Porting Linux to Nabla Containers](https://dev.to/retrage/porting-linux-to-nabla-containers-j3))。なんと日本人の方ですね！

## 類似のソフトウェア

「小さい仮想マシンレイヤ」のような思想を持ったソフトウェアは、[kvmtool](https://github.com/clearlinux/kvmtool) など昔から幾つもありました。Google さんの [gVisor](https://gvisor.dev/) なんかもあります。

近年では AWS さんも [Firecracker](https://www.usenix.org/system/files/nsdi20-paper-agache.pdf) という Lambda 用のシンプルな仮想マシンレイヤを展開していますが、論文の中身を読んだ感じ発想は同じです。Firecracker が出てきたときに「おおっ、とうとうきたか」と思いました(^^)。今のところあちらは Linux を動作させるのがメインで、virtio を使ってるんですけどね。

## もうちょっと中身を知ってみたい人は...

まずは [github のレポジトリ](https://github.com/Solo5/solo5/)を覗いてみましょう。一番良いのは、[ソースコードのドキュメント類](https://github.com/Solo5/solo5/tree/master/docs)をチラ見してみることかと思います。

また、メーリングリストもあるので登録してみると良いと思います。登録の方法は、[README.md](https://github.com/Solo5/solo5#contributing-and-community) に書いてあります。

## おわりに

Solo5 は "Simple is the best" の王道を走っているので、ムダを好まない私にとってはとても好感が持てます。また、シンプルであるが故に hvt の実装はサーバ仮想化の仕組みを理解するための良き教科書になるのではないかと考えています。

個人的には現在 32-bit ARM 用に Solo5 with spt を移植していて、32-bit Ubuntu 上で正常動作するところまで来たのですが...。しかし MirageOS Unikernel 側のモジュールを 32-bit ARM に対応させようとすると一部で厄介事が起こることが分かり、コミュニティとやりとりを開始しつつ Solo5 自体の Pull Request をいつ投げようか様子見中です。
(32-bit ARM はそのうち [Linux kernel で KVM サポートを辞める](https://www.phoronix.com/scan.php?page=news_item&px=Linux-5.7-Kill-32-bit-ARM-KVM)ので、hvt の移植はやらないでしょう)