RustによるIntel SGXプログラミングとSDKの内部実装

いつもお世話になっております。この記事は[Rust Advent Calendar 2019](Rust Advent Calendar 2019)の18日目の記事です。

この記事では、Rustを使ったIntel SGXプログラミングとそのRust-SGX-SDKライブラリの内部実装を紹介していきます。

## Intel SGXとは

Intel SGXはセキュアハードウェアの代表例であるTEEの一種です。Intelが提供する第六世代Skylakeシリーズ以降のプロセッサに搭載しており、EPCと呼ばれる物理メモリ領域を隔離・保護しています。つまり、EPC領域へのアクセスは全てCPUがコントロールし、DMAによるアクセスもできません。（Enclaveの仮想メモリ空間のELRANGE領域がEPCへmappingされます。）また、SGXにおいてはこのメモリは暗号化されており、on-chipのキャッシュでのみ復号化され演算処理が行われます。そして、メモリ暗号化エンジン以外の多くのSGXの機能はmicrocodeで実装されています。
SGXにおける脅威モデルではOS、ハイパーバイザなどのシステムソフトウェアを一切信頼しておらず、万が一これらのシステムソフトウェアが悪意を持っていたとしても安全にアプリケーションが動作することを目指しています。

このようにTEEは安全ではないかもしれないデバイスでも安全にアプリケーションを動作させることを目指しており、信頼すべきはIntelのチップと隔離領域で動作するアプリケーションのみとしています。このような性質上、特に、IoT分野でデバイスのセキュリティ面の課題解決やBlockchain分野でのプライバシー保護への応用に注目が集まっています。

## SGX Programming
SGX上での開発をしていく上では、大きく2つのアプローチが存在します。

- intel-sdk
    - SDKとして提供されているツール群を活用していくアプローチ
- LibOS
    - LibOSのコンテナ上で開発するアプローチ

LibOSではenclaveからのsystem callを含む命令で必要なMinimumなABIをそれぞれ独自で定義している分、標準ライブラリをそのままimportできるので既存ライブラリのコードベースをそのまま活用しやすいことにメリットがありますが、まだまだ対応している関数は少ないのでSGX SDKのEDLを利用するアプローチを紹介していきます。

また、SDK自体はC++で実装されていますが、enclave内プログラムはメモリ安全である方がベターなのでRustによりWrappingされた[rust-sgx-sdk](https://github.com/apache/incubator-teaclave-sgx-sdk)を使います。

### 全体像

SGXプログラミングを行う場合は大きく3つのステップに分けられます。

- EDLファイルの定義
- Enclave外コードの実装
- Enclave内コードの実装

Enclave内で扱うことのできない命令処理をEnclave外のコードで処理し、そのブリッジをEDLが行なっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135415/d304f1ba-65e5-55c6-f027-16a953bfdf83.png)

### EDLファイル
EDLはEnclaveの内側と外側を橋渡しするためのブリッジ関数のインターフェースを定義するためのファイルです。ここで定義したインターフェースがSDKのEdger8rというツールによりコード生成され、Enclave内外で呼ばれることになります。

実際のEDLは以下のように書きます。 （[sgx-time.edl](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/master/edl/sgx_time.edl) の例）

```
enclave {

    include "time.h"

	trusted {
        /* define ECALLs here. */
    };

    untrusted {
        int u_clock_gettime_ocall([out] int *error, int clk_id, [out] struct timespec *tp);
    };
};
```

ここで、trustedパートとunrutedパートに分かれているのは、enclave内からenclave外へ呼び出す関数がocallとして定義し、逆にenclave外からenclave内へ呼び出す関数がecallとして定義されています。引数の`[out]`で呼び出し先（Ocallの場合はHost側）のデータを参照し、`[in]`で呼び出し元（Ocallの場合はEnclave側）のデータを参照します。（より詳細なEDLのSyntaxは[Intel® Software Guard Extensions SDK for Linux* OS](https://01.org/sites/default/files/documentation/intel_sgx_sdk_developer_reference_for_linux_os_pdf.pdf)を参照ください）
Enclave内はsystem callができないno_std環境であるので、disk I/O、network I/Oなどsystem callを行いたい場合はOcallを介して呼び出す必要があります。また、ecallは保護された領域の機密性の高いデータを利用したい場合に用いられます。

それでは、SGX Programmingの概要を整理したところで具体的なイメージを掴むためにrust-sgx-sdkの標準ライブラリの中身を見ていきます。


### sgx_tstd

Enclave内は上述のようにno_std環境ですが、標準ライブラリのサブセットである[sgx_tstd](https://github.com/apache/incubator-teaclave-sgx-sdk/tree/master/sgx_tstd)を以下のようにimportして使うことでEnclave内での標準ライブラリとして扱っていきます。

```rust
use sgx_tstd as std;
```
例えば本来であれば`sgx_tstd::fs::create_dir`はファイルシステムへのアクセスが必要なのでenclave内では用いることはできませんが、`sgx_tstd`内部で上述のocall/ecallを用いることで開発者にとってはsystem callを含む処理も通常の標準ライブラリと同様に提供できています。

[sgx_libc crate](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/master/sgx_libc/src/linux/x86_64/ocall.rs)でEDLに定義されたx86_64用のocall関数を呼び出しています。例えば、`sgx_tstd`で以下のようにファイル・ネットワークI/Oなどのocallを扱います。

```rust
// memory
pub fn u_malloc_ocall(result: * mut * mut c_void, error: * mut c_int, size: size_t) -> sgx_status_t;
// env 
pub fn u_environ_ocall(result: * mut * const * const c_char) -> sgx_status_t;
// file
pub fn u_open_ocall(result: * mut c_int, error: * mut c_int, path: * const c_char, flags: c_int) -> sgx_status_t;
// fd
pub fn u_read_ocall(result: * mut ssize_t, error: * mut c_int, fd: c_int, buf: * mut c_void, count: size_t) -> sgx_status_t;
// time
pub fn u_clock_gettime_ocall(result: * mut c_int, errno: * mut c_int, clk_id: clockid_t, tp: * mut timespec) -> sgx_status_t;
// socket
pub fn u_socket_ocall(result: *mut c_int, errno: *mut c_int, domain: c_int, ty: c_int, protocol: c_int) -> sgx_status_t;
// async io
pub fn u_poll_ocall(result: * mut c_int, errno: * mut c_int, fds: * mut pollfd, nfds: nfds_t, timeout: c_int) -> sgx_status_t;
```

#### sgx_urts

これらのocall関数の処理が実際に実装されているのは、[sgx_urts crate](https://github.com/apache/incubator-teaclave-sgx-sdk/tree/master/sgx_urts) です。`sgx_urts`はenclave外で動作するcrateなので通常のstd環境になります。上記のocallのうち、いくつかを見ていきます。

SocketのBindを行うためにocallで内部で`libc::bind`を呼び出しています。

```rust
#[no_mangle]
pub extern "C" fn u_bind_ocall(error: * mut c_int,
                               sockfd: c_int,
                               address: * const sockaddr,
                               addrlen: socklen_t) -> c_int {
    let mut errno = 0;
    let ret = unsafe { libc::bind(sockfd, address, addrlen) };
    if ret < 0 {
        errno = Error::last_os_error().raw_os_error().unwrap_or(0);
    }
    if !error.is_null() {
        unsafe { *error = errno; }
    }
    ret
}
```
時刻を取得するためにocall内部で`libc::clock_gettime` を呼び出しています。

```rust
#[no_mangle]
pub extern "C" fn u_clock_gettime_ocall(error: * mut c_int, clk_id: clockid_t, tp: * mut timespec) -> c_int {
    let mut errno = 0;
    let ret = unsafe { libc::clock_gettime(clk_id, tp) };
    if ret < 0 {
        errno = Error::last_os_error().raw_os_error().unwrap_or(0);
    }
    if !error.is_null() {
        unsafe { *error = errno; }
    }
    ret
}
```

ここまでの`enclave -> ocall -> edl -> host` の流れを`creat_dir`の処理を例にとって見ていきます。

#### `std::fs::create_dir`

ファイルシステムの扱いはsystem callを必要としますが、sgx-sdk内部でOcallを呼び出すことで、アプリケーション開発者はenclave内でもディレクトリ生成を行うことが可能です。例として、このenclave内での`std::fs::create_dir`ではどのような処理が行われているのか見ていきます。

まず、`sgx_tstd`の[fs module](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/4508450be2c6e169bc592b9d8c6ab3802eb2d557/sgx_tstd/src/fs.rs#L1051-L1053)を見ると、内部的に`sys::fs` moduleで[sgx_trts::libc::ocall::mkdir](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/4508450be2c6e169bc592b9d8c6ab3802eb2d557/sgx_tstd/src/sys/fs.rs#L446-L450)が呼ばれています。

```rust

impl DirBuilder {
    pub fn mkdir(&self, p: &Path) -> io::Result<()> {
        let p = cstr(p)?;
        cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) })?;
        Ok(())
    }
}

mod libc {
    pub use sgx_trts::libc::*;
    pub use sgx_trts::libc::ocall::{open64, fstat64, fsync, fdatasync, ftruncate64, lseek64, fchmod,
                                    unlink, link, rename, chmod, readlink, symlink, stat64, lstat64,
                                    fcntl_arg0, realpath, free, readdir64_r, closedir, dirfd, mkdir, rmdir, opendir, fstatat64};
}

```

この`mkdir`の処理は、[sgx_libc/src/linux/x86_64/ocall.rs](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/4508450be2c6e169bc592b9d8c6ab3802eb2d557/sgx_libc/src/linux/x86_64/ocall.rs#L1164-L1181) で実装されており、`u_mkdir_ocall`を呼び出しています。

```rust
pub unsafe fn mkdir(pathname: * const c_char, mode: mode_t) -> c_int {
    let mut error: c_int = 0;
    let mut result: c_int = 0;
    let status = u_mkdir_ocall(&mut result as * mut c_int,
                               &mut error as * mut c_int,
                               pathname,
                               mode);

    if status == sgx_status_t::SGX_SUCCESS {
        if result == -1 {
            set_errno(error);
        }
    } else {
        set_errno(ESGX);
        result = -1;
    }
    result
}
```

これはEnclave外の処理を呼び出すocall関数なので、[sgx_file.edl](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/4508450be2c6e169bc592b9d8c6ab3802eb2d557/edl/sgx_file.edl#L54) で定義されています。

```
int u_mkdir_ocall([out] int *error, [in, string] const char *pathname, uint32_t mode);
```

そして、実際の`u_mkdir_ocall`の処理は`sgx_urts` crateの[file module](https://github.com/apache/incubator-teaclave-sgx-sdk/blob/e08dff20783232f4f872838cbac62f86946bef3c/sgx_urts/src/file.rs#L376-L389)で実装されています。

```rust
#[no_mangle]
pub extern "C" fn u_mkdir_ocall(error: * mut c_int,
                                pathname: * const c_char,
                                mode: mode_t) -> c_int {
    let mut errno = 0;
    let ret = unsafe { libc::mkdir(pathname, mode) };
    if ret < 0 {
        errno = Error::last_os_error().raw_os_error().unwrap_or(0);
    }
    if !error.is_null() {
        unsafe { *error = errno; }
    }
    ret
}
```

以上は、`std::fs::create_dir`の例でしたが、他の標準ライブラリの関数に対しても内部でocallの処理を行い、trsuted partsとuntrusted partsのseparationを意識しなくても開発できるようになっています。

## まとめ
このようにSGX用の標準ライブラリを用いることで多くの処理が開発者自身がecall/ocallを意識せずとも用いられるようになってきています。 次回はTLS通信をSGX上でどう実装するかという具体例を紹介していきたいと思います。
皆さんもぜひ、よいSGXプログラミングライフを！