Skip to content

Commit

Permalink
add 04
Browse files Browse the repository at this point in the history
  • Loading branch information
dingelish committed Jul 30, 2018
1 parent 050de96 commit 572e5d9
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 0 deletions.
263 changes: 263 additions & 0 deletions 04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
# 移植

这一篇主要讨论如何把 Rust crate 移植到 Rust SGX 环境中。我们已经把一些相当复杂的库移植到了 Rust SGX 环境中,包括 rustls/webpki/ring 这个完整的 TLS stack。对比之下 Intel 移植的 [SgxSSL](https://github.com/intel/intel-sgx-ssl/blob/master/Linux/package/docs/Intel(R)%20Software%20Guard%20Extensions%20SSL%20Library%20Linux%20Developer%20Guide.pdf) 就简直是个“残疾”。这里就体现出了 Rust 相对于 C/C++ 的巨大优势:可移植性超强。

不过在讨论移植之前,先看一个极端例子:

```
if f.attr != 0600 then
print '打死我也不说'
else
print '我的密码是deadbeaf'
```

这例子以文件属性作为输入,判断其是否满足特定条件。一旦满足则吐出一个秘密。这个例子可能显得有些夸张——谁会用文件属性做输出密码的条件嘛!相比之下,下面这个例子就显得比较现实一些。

```
if envattr["GC_FACTOR"] != empty then
gc_factor = envattr["GC_FACTOR"]
```

这个逻辑看上去就合理多了是不是。通过读取环境变量的值来设定内存回收的参数,并且事实上 [rpython](https://github.com/mozillazg/pypy/blob/master/rpython/memory/gc/incminimark.py) 就是这么做的。

但是这样做**不适用于** SGX enclave,因为无论是文件属性,还是环境变量,都不在 SGX 的保护范围之内。虽然 Scone 提供了一个所谓的 [CAS](https://sconedocs.github.io/SCONE_CAS/) 机制,试图让 enclave 跑在可信的环境变量设置之下,但是这依旧**破坏了 SGX 的信任模型**:只信任 CPU 和 Intel。攻击者完全可以通过打破 untrusted 部分的逻辑来污染文件属性、环境变量等并不被 SGX 保护的值,来干扰 SGX enclave 的执行逻辑。传统程序里广泛存在这样的逻辑,因此无论如何设计兼容层,使传统程序直接跑在 SGX enclave 的努力都是徒劳,因为他们依赖于**不可信的输入**

这就又回到“可信”的讨论上来。事实上“可信”的概念并没有个特别清晰的定义。翻到了一篇[文献](http://www.jos.org.cn/jos/ch/reader/create_pdf.aspx?file_no=5024&journal_id=jos)里搜集了一些对于“可信”的定义(见表1)。其中 ISO/IEC15408 的定义比较有趣:“一个可信的组件、操作或者过程的行为在任意操作条件下是行为可预测的,并能很好地抵抗应用程序软件、病毒以及一定的物理干扰造成的破坏”。这个“可预测”就显得意味深长了。现代操作系统中,不可预测的东西实在太多。而 SGX 的设计理念就抛弃了非常多的“不可预测”,比如根本就**不支持** syscall。这导致软件的移植是一件麻烦事——要重新考虑每个输入是否是可信的,包括所有的隐式输入(例如上述文件属性、环境变量等)。并且直接导致了基于“重打包”的 Graphene, Scone 并不能完全保护 SGX 程序,只是“看上去很美”。

那如何构造一个可信的 SGX enclave 呢?答案当然是使用我们的 [rust-sgx-sdk](https://github.com/baidu/rust-sgx-sdk) 啦!我们提供了一个定制版的 `sgx_tstd` 用于取代传统的 `std`,把不可信输入(例如`fs`)都移到了 `sgx_tstd::untrusted` 空间下。如果把一个直接使用不可信输入的库移植进来,那么必然会在编译时报错:找不到符号。并且默认不打开 `net` `time` 等 feature。如果要使用这些不可信输入,那么建议开发人员仔细思考并使用 `untrusted` 下的功能,并重新设计这部分逻辑。

关于随机数,我们现在提供了 `sgx_rand` 来满足部分需求。许多项目依赖的 `rand` 我还没来得及移植。

## Cargo 和 Xargo

简单的来说,如果用 cargo 编译,那么就需要手工 clone 代码然后将 crate 处理为 Rust-SGX 适用的形式。如果用 xargo 编译,那么在 crate 没有使用不可信输入(没有使用 `std::{fs,path,env,net,time}` 等)的情况下),直接在 `Cargo.toml` 里引用然后 `XARGO_SGX=1 make` 就可以了。

具体来说,如果是 xargo 编译 (在 Rust SGX 项目的例子代码中是 `XARGO_SGX=1 make`),则完全不用理会所需要的 crate 是不是支持 `no_std`,因为 xargo 时已经用 `sgx_tstd` 彻底换掉了 `std`,并且是一个 `std` 环境。而 cargo 编译时我们需要将整个 enclave 限定为 `#![no_std]` 然后通过一个相对比较丑陋的 `extern crate sgx_tstd as std` 来重新导入 `std`。这里就涉及到了一个 `no_std``std`[隐藏差别](https://doc.rust-lang.org/std/prelude/): `std::prelude`。在 `std` 环境下,除了默认 `extern crate std` 之外,编译器还自动加了一行 `use std::prelude::v1::*;` 在每个 `.rs` 的开头。所以,在 Rust SGX 环境下,是要手工补上这一行的。具体怎么操作,参见后面的实例。


## 直接使用支持 no_std 的 crate

作为最简单的“移植”(其实也不算移植了),利用第三方库支持 `no_std` 的性质,可以在 Rust SGX + cargo 环境下直接引用 crates.io 上的库。这里以我常用的 [itertools](https://github.com/bluss/rust-itertools) 为例,其 `Cargo.toml` 如是说:

```
[features]
default = ["use_std"]
use_std = []
```
默认是开启了 `use_std` 这个 feature。于是如果我们要支持 cargo 编译 enclave,就需要关掉这个默认打开的 feature,于是在 `Cargo.toml` 里这么引用:

```
[dependencies]
itertools = { version = "0.7.8" , default-features = false, features = []}
```

然后正常的 `extern crate itertools` 就可以了。

## Cargo 环境下的移植

用一个递归来展示这个移植过程

```
def 移植(self):
if self支持 no_std then
不用修改,直接在依赖处配置好 no_std 的 features
return
# 移植依赖项 (忽略dev-dependencies)
for each dep of self.dependencies
移植 dep
# 移植自身
(1) wget 库代码 && tar xzf
(2) 编辑 Cargo.toml 修改每个依赖项为移植后的依赖项
(3) 编辑 src/lib.rs 添加特定header(见后文)
(4) 编辑每个源文件 添加 use std::prelude::v1::*;
(5) 仔细review每个使用 fs/path/net/time/env 等不可信输入的地方,修正那里的逻辑
(6) 检查每个 platform dependent 的 feature,将其固定为只适用于 linux-x86_64 的逻辑(因为 linux-SGX 就只有这个环境)
(7) 测试 `cargo build` 是否通过
return
```

(1) 以 [http](https://crates.io/crates/http) 为例。首先看到他的 crate name 是 `http`,目前最新的版本是 0.1.8。那么获取其源代码的命令是:

```
wget https://crates.io/api/v1/crates/http/0.1.8/download -O http-0.1.8.crate
```

这是个 tgz 文件,可以直接用 `tar -xzf` 解压。

(2) 进入源代码目录后,首先用 `cargo tree``cargo install cargo-tree`安装)来看其依赖:

```
http v0.1.8 (file:///tmp/http-0.1.8)
[dependencies]
├── bytes v0.4.9
│ [dependencies]
│ ├── byteorder v1.2.3
│ └── iovec v0.1.2
│ [dependencies]
│ └── libc v0.2.42
├── fnv v1.0.6
└── itoa v0.4.2
[dev-dependencies]
...
```

于是需要先处理 `iovec v0.1.2`。注意这里他依赖了 `libc`。在 Rust SGX 环境下是**不能使用`libc`**的,因为**没有**(手动狗头)。我们把`libc`里兼容 SGX 的部分提炼出来,放入了 `sgx_trts::libc`。如果这个满足不了需要的话,那么就没有办法了!

直接下载 `iovec v0.1.2` 的代码,分析其如何依赖于`libc`

```
~/iovec-0.1.2 $ grep -R libc .
./Cargo.toml:libc = "0.2"
./src/sys/unix.rs:use libc;
./src/sys/unix.rs: unsafe fn iovec(&self) -> libc::iovec {
./src/sys/unix.rs: mem::transmute(libc::iovec {
./src/sys/unix.rs: mem::transmute(libc::iovec {
./src/unix.rs:use libc;
./src/unix.rs:/// Convert a slice of `IoVec` refs to a slice of `libc::iovec`.
./src/unix.rs:pub fn as_os_slice<'a>(iov: &'a [&IoVec]) -> &'a [libc::iovec] {
./src/unix.rs:/// Convert a mutable slice of `IoVec` refs to a mutable slice of `libc::iovec`.
./src/unix.rs:pub fn as_os_slice_mut<'a>(iov: &'a mut [&mut IoVec]) -> &'a mut [libc::iovec] {
./src/lib.rs:extern crate libc;
```

看上去`iovec`只依赖于`libc::iovec`这个结构体的定义!那就好说了,`sgx_trts::libc::iovec`这个我们是有的。于是可以如下修改`Cargo.toml`

```
[depdendencies]
sgx_trts = "=1.0.1"
sgx_tstd = "=1.0.1"
```

这里去掉了关于 `unix``windows` 的feature。并且在整个项目里都要删除所有`windows`下的部分,无条件保留`unix`下的部分,并删除这两个feature。

(3) 在`lib.rs`里做如下修改:

```
#![no_std]
extern crate sgx_tstd as std;
extern crate sgx_trts;
mod sys;
use std::{ops, mem};
pub mod unix;
```

(4) 在`src/unix.rs`里做如下修改:

```
use std::prelude::v1::*;
use IoVec;
use sgx_trts::libc;
use std::mem;
```

`src/sys/mod.rs`改成:

```
mod unix;
pub use self::unix::{
IoVec,
MAX_LENGTH,
};
```

这里去掉了所有的`unix`和不必要的代码。

`src/sys/unix.rs`改成:

```
use std::prelude::v1::*;
use sgx_trts::libc;
use std::{mem, slice, usize};
```

于是看起来就都处理干净了,这时候试试编译:

```
~/iovec-0.1.2 $ cargo b
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading sgx_tstd v1.0.1
Downloading sgx_unwind v0.0.2
Downloading sgx_tprotected_fs v1.0.1
Downloading sgx_alloc v1.0.1
Compiling cfg-if v0.1.4
Compiling libc v0.2.42
Compiling sgx_unwind v0.0.2
Compiling sgx_alloc v1.0.1
Compiling sgx_tprotected_fs v1.0.1
Compiling filetime v0.1.15
Compiling sgx_build_helper v0.1.0
Compiling sgx_tstd v1.0.1
Compiling iovec v0.1.2 (file:///tmp/iovec-0.1.2)
warning: unused import: `std::prelude::v1::*`=====================> ] 14/15: iovec
--> src/unix.rs:21:5
|
21 | use std::prelude::v1::*;
| ^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unused_imports)] on by default
Finished dev [unoptimized + debuginfo] target(s) in 5.18s
```

再删掉 `src/unix.rs` 下的 `prelude` 引用,这个应该就做完了。

然后递归操作其他剩下的 crate 即可。如果想看答案的话可以参考我移植的 [http](https://github.com/baidu/rust-sgx-sdk/tree/master/third_party/http), [fnv](https://github.com/baidu/rust-sgx-sdk/tree/master/third_party/rust-fnv), [iovec](https://github.com/baidu/rust-sgx-sdk/tree/master/third_party/iovec), [bytes](https://github.com/baidu/rust-sgx-sdk/tree/master/third_party/bytes)

## 同时支持 cargo 和 xargo

这有一点点技巧性。首先需要明晰在 xargo 环境中是有一个所谓的 sysroot,包含了所有这个 target 下“环境自带”的 crate。在 Rust SGX 环境中的 sysroot 大概包括的 crates 可以查看[这里](https://github.com/baidu/rust-sgx-sdk/blob/master/release_notes.md#about-xargos-sysroot)

`sgx_trts`为例,在`cargo`环境下,需要在`Cargo.toml`里显式引用`sgx_trts`,但是在纯`xargo`环境下则不需要。所以,同时支持 `cargo + xargo``Cargo.toml`长成这个样子:

```
[target.'cfg(not(target_env = "sgx"))'.dependencies]
sgx_tstd = "=1.0.1"
sgx_trts = "=1.0.1"
```

这里的语义不难理解。值得注意的是`target_env = "sgx"` 这里的值的来源是平台配置json:`x86_64-unknown-linux-sgx.json`。这里声明了:`"env": "sgx"`。所以在支持`xargo`时还需要这个json文件的。

对于之上的(3)步,也有一些改变。不再是向上述所说直接配置为`#![no_std]`,而是加入了一定的条件:

```Rust
#![cfg_attr(not(target_env = "sgx"), no_std)]
#![cfg_attr(target_env = "sgx", feature(rustc_private))]

#[cfg(not(target_env = "sgx"))]
#[macro_use]
extern crate sgx_tstd as std;

extern crate sgx_trts;
```

这里的 `rustc_private` 是必须的,不然`xargo`不允许从sysroot里读取`sgx_trts`,而是强制用户从 crates.io 下载 `sgx_trts`

排除最后一行`sgx_trts`不管,头部的5行是必须的,我把它叫做 “The Magic 5”。

对于一个具有复杂依赖关系的 Rust SGX enclave 来说,如果要用 `xargo` 编译,那么就一定需要为其指明 target json 和提供一个 `Xargo.toml` 来指导 `xargo`。如果要单独测试一个 crate 是否移植成功,则可以在其代码根目录(`Cargo.toml`所在的目录)下放置这两个文件,然后:

```
$ cargo b # 测试 cargo build
$ RUST_TARGET_PATH=$(pwd) xargo build --target x86_64-unknown-linux-sgx # 测试 xargo build
```

如果不指明`RUST_TARGET_PATH`,则`xargo`会在编译每个依赖项时去其代码根目录下寻找`x86_64-unknown-linux-sgx.json`,少一个都不行。这个“默认去每个依赖项下寻找json”的行为是在`xargo`的某个版本加上去的,为此我们不得不把json复制到了每个third_party的目录下……

## 特殊的feature处理

这里以[`rust-crypto`](https://github.com/baidu/rust-sgx-sdk/tree/master/third_party/rust-crypto)为例。注意到原版[`rust-crypto`](https://github.com/DaGenix/rust-crypto/)对于`aesni`指令集的处理是根据这么写的:

```
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub mod aesni;
```

所有支持 SGX 的 cpu 都是有 aesni 支持的(参考 [sgx-hardware](https://github.com/ayeks/SGX-hardware)),所以我们可以简单的强制开启`aesni`模块。我们 Rust SGX 的编译过程中没定义`target_arch`,所以就需要手工处理掉所有的 `target_arch` 相关判断,让这个库无条件的启用 `aesni` 模块。(其实加上`target_arch`可能更优雅一点?)

# 写在最后

优秀的可移植性是 Rust SGX 优于 C/C++ SGX 的最大特点。在此基础之上我们可以复用非常多的轮子来构造我们强壮的 Rust SGX enclave。我们已经移植了 rustls/webpki/ring、rust-crypto、wasmi、serde、protobuf 等相当复杂的库,提供了在 enclave 内 terminate TLS 的能力,以及执行任意 WebAssembly 程序的能力。此外,借助这个 TLS stack,在 enclave 内可以轻松构建出 https client 和 server,中间人彻底拜拜~(但是没法调试也是很蛋疼的……)

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ License是取的比较严的 [CC-BY-NC-ND](https://creativecommons.org/licenses/

最后打个广告,这一年多做的 [rust-sgx-sdk](https://github.com/baidu/rust-sgx-sdk) 自认为还是挺靠谱的。是唯一一个在 Intel SGX SDK 首页上被推荐的第三方SDK,已经有几个基于可信计算的区块链创业项目应用了这个SDK,比如 Berkeley Oasis Labs 的 [Ekiden](https://arxiv.org/abs/1804.05141),MIT 的 [Engima](https://github.com/enigmampc/enigma-core) 等等。

更新:[Chainlink](https://github.com/cryptape) 已经应用了 rust-sgx-sdk 并使用了我们移植的 WebAssembly 解释器! [Cargo.toml](https://github.com/smartcontractkit/chainlink/blob/393cbc896fe63c4ef44ab71218f0e89366d9b4e5/sgx/enclave/Cargo.toml)


## Index

[00 SGX能做什么](00.md)
Expand All @@ -24,6 +27,8 @@ License是取的比较严的 [CC-BY-NC-ND](https://creativecommons.org/licenses/

[03 Edger8r upgrade 参考译文](03.md)

[04 移植](04.md)

# License
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License</a>.

0 comments on commit 572e5d9

Please sign in to comment.