forked from dingelish/SGXfail
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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,中间人彻底拜拜~(但是没法调试也是很蛋疼的……) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters