Rust crate 移植到Rust SGX环境中

移殖rust-sgx-sdk

  • 无论是文件属性还是环境变量,都不再SGX的保护范围之内,虽然SCONE提供了一个CAS机制,试图让enclave跑在可信的环境 变量设置之下,但是这依旧破坏了SGX的信任模型:只信任CPU和Intel。攻击者完全可以通过打破untrusted部分的逻辑来污染文件属性、环境变量等并不被SGX保护的值,来干扰SGX enclave的执行逻辑。传统程序广泛存在这样的逻辑,因此无论如何设计兼容层,使传统程序直接跑在SGX enclave的努力都是徒劳的,因为他们依赖于不可信的输入。
  • SGX 的设计理念就抛弃了非常多的“不可预测”,比如根本就不支持 syscall。这导致软件的移植是一件麻烦事——要重新考虑每个输入是否是可信的,包括所有的隐式输入(例如上述文件属性、环境变量等)。并且直接导致了基于“重打包”的 Graphene, Scone 并不能完全保护 SGX 程序,只是“看上去很美。
  • 提供了一个定制的 sgx_tstd 用于取代传统的 std,把不可信输入(例如fs)都移到了 sgx_tstd::untrusted 空间下。
  • 如果把一个直接使用不可信输入的库移植进来,那么必然会在编译时报错:找不到符号。并且默认不打开net, time等feature。如果要使用不可信输入,建议仔细考虑思考并且使用untrusted下的功能,并重新设计这部分逻辑。

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 的隐藏差别: std::prelude。在 std 环境下,除了默认 extern crate std 之外,编译器还自动加了一行 use std::prelude::v1:😗; 在每个 .rs 的开头。所以,在 Rust SGX 环境下,是要手工补上这一行的。具体怎么操作,参见后面的实例。

直接使用支持no_std的crate

  • 利用第三方库支持no_std的性质,可以在Rust SGX + cargo环境下直接引用crates.io上的库。这里以我常用的 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为例子,首先看到他的crate name 是http:
wget https://crates.io/api/v1/crates/http/0.1.8/download -O http-0.1.8.crate
  • 这是个 tgz 文件,可以直接用 tar -xzf 解压。
  1. 进入源代码目录后,首先用crate 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只依赖于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。
  1. 在lib.rs里做如下修改:
#![no_std]
extern crate sgx_tstd as std;
extern crate sgx_trts;

mod sys;
use std::{ops, mem};
pub mod unix;
  1. 在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, fnv, iovec, bytes。

同时支持cargo和xargo

这有一点点技巧性。首先需要明晰在 xargo 环境中是有一个所谓的 sysroot,包含了所有这个 target 下“环境自带”的 crate。在 Rust SGX 环境中的 sysroot 大概包括的 crates 可以查看这里。

以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],而是加入了一定的条件:

#![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为例。注意到原版rust-crypto对于aesni指令集的处理是根据这么写的:

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

所有支持 SGX 的 cpu 都是有 aesni 支持的(参考 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,中间人彻底拜拜~(但是没法调试也是很蛋疼的……)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值