├── 00.md ├── 01.md ├── 02.md ├── 03.md ├── 04.md ├── 05.md ├── 06.md ├── LICENSE └── README.md /00.md: -------------------------------------------------------------------------------- 1 | # SGX 能做什么 2 | 3 | 或多或少大家在读本文之前都听说过Intel SGX如何如何厉害云云,我想说的是SGX其实只解决了可信计算的一小部分,距离大多数人脑中的理想还很远。 4 | 5 | 简单的说 Intel SGX 试图**在用户相信Intel的前提下**保证: 6 | 7 | * 编译生成的 enclave.sign.so 是目前正在运行的这个 enclave.sign.so 8 | * 这个 SGX enclave 里的 secret 不会通过非 side channel 泄露出去 9 | * 这个 enclave 具体跑在哪个 platform (=CPU+mb) 上是不可知的 10 | 11 | 但是这**不包括**: 12 | 13 | * 我写下的代码是正在运行的代码。这需要一个**诚实的编译器**,和许多诚实的库。不是SGX的事。 14 | * SGX enclave会一直工作。因为它毕竟还是依赖于内核分配资源给他。内核不分配内存,线程被杀死这种事一旦发生,enclave也会停下来的。 15 | * 保证 enclave 拿到的“外界输入”都是正确的。试想一下,如果把一个python解释器放进SGX enclave,在解释执行的python源代码中写下 `import os` 的时候会发生什么?Python解释器会去文件系统中找到`os`并把它读进内存。然而SGX的信任边界并不能保证这个读进来的`os`是被期待的`os`!任何攻击者都可以篡改这个`os`导致在enclave里执行恶意逻辑!(那些在enclave里把syscall包一层从而支持任意程序的在我看来都是“使用SGX”但是没有被SGX保护的典型)。 16 | 17 | SGX的一个典型应用就是做多方安全计算(Secure Multiparty Computing)。举例如[百万富翁问题](https://en.wikipedia.org/wiki/Yao%27s_Millionaires%27_Problem)。借助SGX可以构造一个可信的裁判来解决: 18 | 19 | * 两个大富翁首先都得相信Intel。 20 | * 两个大富翁需要共同review这个裁判enclave的代码,取得共识,都认为这个enclave是公正的。 21 | * 用两人都认可的编译环境生成enclave.sign.so。 22 | * 把这个enclave在任何SGX执行环境上跑起来。 23 | * 两人分别remote attest这个跑起来的enclave,验证其report是否完全满足安全需求。 24 | * 分别把自己的财产数通过安全的信道传给enclave,enclave做出判断,返回给两人结果。 25 | 26 | 看起来很美,但实际上坑还是挺多的……后面再具体分析吧。 27 | 28 | # Intel的努力 29 | 30 | 为了支持这个可信计算环境,Intel做了非常多工作的,大概包括(不限于): 31 | 32 | * 一个精巧的 [Memory Encryption Engine](https://eprint.iacr.org/2016/204.pdf),保证了 enclave 内的数据只有 enclave 自身才能访问明文。出了CPU就是密文,就算直接截胡memory bus也听不到有意义的数据。 33 | * 一套基于 EPID 的组签名机制,保证 platform (=CPU+mb)的匿名性。 34 | * remote attestation的IAS支持。 35 | * 一套 trusted/untrusted 的基础库。 36 | * [论坛](https://software.intel.com/en-us/forums/intel-software-guard-extensions-intel-sgx) 37 | 38 | # 从入门到放弃 39 | 40 | 纵使世界上有那么多关于SGX的论文、分析,我想说的是,不自己看一遍Intel官方的手册、文档和读SDK、driver代码的话,是很难理解SGX实现起来是怎么一回事的。但是这些东西太无聊,读着读着就很容易——放弃了!秘而不宣的东西很多都藏在代码和官方文档里,还是得耐着性子看一遍。我会尽量在这些blog里提供一些其他地方没有的分析——如果还没放弃的话。 41 | 42 | 无论如何——我建议每个人都亲自读 Intel SGX Developer Guide([2.2版本](https://download.01.org/intel-sgx/linux-2.2/docs/Intel_SGX_Developer_Guide.pdf))。这份40页的小书还是蛮有意思,可以体会体会Intel是怎么个口径的。 43 | 44 | # 有用的资源 45 | 46 | * 力推 [Platform Embedded Security Technology Revealed](https://link.springer.com/book/10.1007/978-1-4302-6572-6)。如果对可信计算没概念的话,耐着性子读完这本书会很有帮助! 47 | * Intel SGX [主页](https://software.intel.com/en-us/sgx) 48 | * Intel SGX SDK [主页](https://software.intel.com/en-us/sgx-sdk) 49 | * Intel Open Source 01.org 上的 SGX for linux [主页](https://01.org/intel-softwareguard-extensions) 一般来讲这里更新最快 50 | * Intel 官方的 [linux-sgx-sdk](https://github.com/intel/linux-sgx) 51 | * Intel 官方的 [linux-sgx-driver](https://github.com/intel/linux-sgx-driver) 52 | * 一份 Remote Attestation 实现 [linux-sgx-remoteattestation](https://github.com/svartkanin/linux-sgx-remoteattestation) 53 | * 一百多页的 [Intel SGX Explained](https://eprint.iacr.org/2016/086.pdf) 基本读不完就放弃了 54 | * [SGX-hardware](https://github.com/ayeks/SGX-hardware) 哪些CPU/主板/云厂商提供SGX支持,可以参考。也包含一个检测sgx是否支持/开启的小程序。 55 | 56 | 57 | -------------------------------------------------------------------------------- /01.md: -------------------------------------------------------------------------------- 1 | ## 运行第一个SGX程序 2 | 3 | 本系列文章并不是客观的“如何写SGX程序”教程,而是一份**非常主观**的SGX导读,夹杂了本人对**当下的SGX实现**的解读。目前linux sgx套件的版本是2.2. 4 | 5 | 本系列文章中本人想引导读者对SGX的能力有一个更透彻的认识——其实是对“可信+保密”这个概念在实现成程序的时候究竟是什么样子展开讨论。其中不乏反问”这究竟是应该被信任的吗“之类的提问。在我过去的SGX推广经验中,看到的大多数问题都源自于对”可信+保密“理解不透彻。毕竟写一个传统应用和一个可信计算基是截然不同的两码事,没有经验也实属正常。本人在研究SGX的过程中也在不断的反思这两个问题。 6 | 7 | 这一节先从SGX运行环境的结构开始。 8 | 9 | Intel SGX 程序通常分成两部分:untrusted和trusted。trusted包括了用户自己写的SGX enclave。用户自己的untrusted app需要和Intel提供的untrusted runtime等运行时库配合使用。 10 | 11 | 此外,为了支持Intel SGX程序的执行,还需要的东西包括:一个untrusted部分的守护进程aesmd,提供"Application Enclave Service Manager";几个必备的官方Enclave:Launch Enclave (le), Quoting Enclave (qe), Platform Service Enclave (pse), Provisioning Enclave (pve), Provisioning Certification Enclave (pce), Reference Launch Enclave (ref_le, 2.1.3新增, 具有提供Flexible launch control的能力);和一些配套的untrusted AE接口。 12 | 13 | > Application Enclave Service Manager (aesm), 是Intel SGX的系统组件,主要提供了 14 | SGX Enclave 启动支持,密钥配置,远程认证等服务。 15 | 16 | 用一个在rust-sgx-sdk里提供的图来说明整体结构吧: 17 | ![sgx and mesalock linux](https://raw.githubusercontent.com/baidu/rust-sgx-sdk/master/documents/mesa.png) 18 | 19 | 左图和右图分别是两种在docker里运行SGX程序的结构。图中展示的信息包括: 20 | 21 | 1. SGX的内核驱动暴露一个`/dev/isgx`的misc device给用户态。用户态的AESM守护进程和uae service等经由这个misc device为用户程序的untrusted部分提供create/destroy enclave等的能力。 22 | 2. AESM的守护进程aesmd会打开一个domain socket: aesm.socket给用户态程序的urts(untrusted runtime service)库和uae_service库提供支持。AESM对于用户来说“应该是个黑盒子”。用户只需要通过urts和uae_service提供的接口来实现业务逻辑就可以了。 23 | 24 | 其他的后面再说。 25 | 26 | 那么从之上的讨论中可以看出:想在真实硬件环境中运行一个SGX程序的前提包括平台提供isgx设备和全套的aesm服务和urts/uae_service的动态库。这里先抛开基于Intel ME提供的PSE不谈,后面再说。 27 | 28 | 为了方便开发人员,Intel提供了一套软件模拟环境。这套模拟环境可以通过在编译时指定`SGX_MODE=SW`打开。启用`SW`环境后,编译时会链接所有库的模拟执行版本,例如`libsgx_trts_sim.a`, `libsgx_tservice_sim.a`等。对应的untrusted部分也有模拟环境:`libsgx_uae_service_sim.so`, `libsgx_urts_sim.so`. 所以,在没有SGX硬件的支持下,也是可以通过软件模拟的方式来玩SGX的(但是做不了remote attestation,并且没有PSE)。 29 | 30 | 可以参考 [SGX-Hardware](https://github.com/ayeks/SGX-hardware) 来确认自己的硬件是否支持SGX。如果CPU+主板都支持的,还需要在BIOS里将SGX模式设置为(Enable)。如果设置为了Software Control,那么在写代码时候还要多写两行,不推荐。如果SGX设置里能看到`OWNEREPOCH`的设置的话,那恭喜你,你的主板对SGX的支持比较完整。 31 | 32 | ## 用纯软件模式+docker模拟SGX环境实验 33 | 34 | 这甚至可以在macOS下完成,只要安装了[docker-for-mac](https://docs.docker.com/docker-for-mac/)。无论在什么平台上,只要有了docker,都可以跑。为了方便,直接用我项目里提供的docker做实验。 35 | 36 | ```bash 37 | $ docker pull teaclave/teaclave-build-ubuntu-1804-sgx-2.9.1:latest 38 | $ git clone https://github.com/apache/incubator-teaclave-sgx-sdk.git 39 | $ docker run --rm -v /your/path/to/incubator-teaclave-sgx-sdk:/root/sgx -ti teaclave/teaclave-build-ubuntu-1804-sgx-2.9.1:latest 40 | root@docker:~# rustup toolchain list 41 | nightly-2018-04-12-x86_64-unknown-linux-gnu (default) 42 | root@docker:~# cd /root/sgx/samplecode/hello-rust 43 | root@docker:~/sgx/samplecode/hello-rust# SGX_MODE=SW make 44 | root@docker:~/sgx/samplecode/hello-rust# cd bin 45 | root@docker:~/sgx/samplecode/hello-rust/bin# ./app 46 | [+] Init Enclave Successful 30696131264514! 47 | This is a normal world string passed into Enclave! 48 | This is a in-Enclave Rust string! 49 | [+] say_something success... 50 | ``` 51 | 52 | ## 用硬件模式+docker真实执行 53 | 54 | 这里采取结构图左侧的方法。要求:CPU有SGX功能+主板开启了SGX支持(SGX为Enabled)。建议使用Ubuntu Desktop 16.04 x64。 55 | 56 | 首先是安装[驱动](https://download.01.org/intel-sgx/linux-2.2/ubuntu64-desktop/sgx_linux_x64_driver_dc5858a.bin)。在左图中AESM是跑在docker内的所以不需要Host OS安装Intel SGX PSW。安装完后确保`/dev/isgx`存在即可。 57 | 58 | ```bash 59 | $ docker pull baiduxlab/sgx-rust 60 | $ git clone https://github.com/baidu/rust-sgx-sdk.git 61 | $ docker run --rm -v /your/path/to/rust-sgx:/root/sgx --device /dev/isgx -ti baiduxlab/sgx-rust 62 | root@docker:~# rustup toolchain list 63 | nightly-2018-04-12-x86_64-unknown-linux-gnu (default) 64 | root@docker:~# /opt/intel/sgxpsw/aesm/aesm_service 65 | aesm_service[17]: The server sock is 0x560f782bf960 66 | aesm_service[17]: [ADMIN]Platform Services initializing 67 | aesm_service[17]: [ADMIN]Platform Services initialization failed due to DAL error 68 | aesm_service[17]: [ADMIN]White list update request successful for Version: 36 69 | root@docker:~# cd /root/sgx/samplecode/hello-rust 70 | root@docker:~/sgx/samplecode/hello-rust# make 71 | root@docker:~/sgx/samplecode/hello-rust# cd bin 72 | root@docker:~/sgx/samplecode/hello-rust/bin# ./app 73 | [+] Home dir is /root 74 | [-] Open token file /root/enclave.token error! Will create one. 75 | [+] Saved updated launch token! 76 | [+] Init Enclave Successful 2! 77 | This is a normal world string passed into Enclave! 78 | This is a in-Enclave Rust string! 79 | [+] say_something success... 80 | ``` 81 | 82 | 结构图右侧的运行方法我们以后再谈。 83 | 84 | ## 配置本机SGX环境+真实执行 85 | 86 | 这里依旧不讨论PSE功能的支持。 87 | 88 | 首先还是照例安装[驱动](https://download.01.org/intel-sgx/linux-2.2/ubuntu64-desktop/sgx_linux_x64_driver_dc5858a.bin),确认`/dev/isgx`存在后安装[PSW](https://download.01.org/intel-sgx/linux-2.2/ubuntu64-desktop/sgx_linux_x64_psw_2.2.100.45311.bin)。确认`aesmd`开启后安装[Intel SGX SDK](https://download.01.org/intel-sgx/linux-2.2/ubuntu64-desktop/sgx_linux_x64_sdk_2.2.100.45311.bin)。这里建议安装到`/opt`。安装完SDK后会提示执行一次`source ...`,就执行一下。 89 | 90 | 然后进入SDK的安装目录,再进入`SampleCode/SampleEnclave`目录,直接`make`再`./app`即可。 91 | 92 | # 剖析hello-rust 93 | 94 | [hello-rust](https://github.com/baidu/rust-sgx-sdk/tree/master/samplecode/hello-rust)是第一个全Rust的Intel SGX程序。这里的“全Rust”指的是untrusted和trusted部分均用Rust编写。 95 | 96 | 首先看`app`目录下的untrusted部分。其`Cargo.toml`非常简单,只包含两个依赖项:`sgx_types`和`sgx_urts`。顾名思义,`sgx_types`就是数据结构和函数接口定义;`sgx_urts`是SGX的untrusted runtime service的Rust接口。对于一个untrusted端app来说,不考虑PSE、uprotected_fs等的情况下,这两个依赖项就足够创建、关闭enclave了。 97 | 98 | 再看`app/build.rs`。这个文件会将编译选项输出给`rustc`。可以看到其主要做的事情是:(1)帮助rustc找到SGX SDK的位置,(2)帮助rustc找到Enclave EDL所生成的Enclave_u的位置,(3)帮助rustc link到正确的urts库(HW or SW)。 99 | 100 | 这里引入了一个EDL的概念。EDL是Intel给定的一种文件格式,非常近似于C的header file写法。它规定了Enclave的边界ECALL/OCALL的定义。Intel提供了一个由Ocaml编写的EDL编译器[edger8r](https://github.com/intel/linux-sgx/tree/master/sdk/edger8r/linux),用于将每一个输入的EDL转化为(1)Enclave_u.c+Enclave_u.h + (2) Enclave_t.c + Enclave_t.h。这四个文件中包含了ECALL/OCALL中所引入的必要的“额外`proxy function`和`marshaling`数据结构”用于完成ECALL/OCALL操作(Intel对于某些CVE的patch包括了在这些stub中插入`lfence/sfence`)。当我们在app中调用导出的ECALL函数时,控制流会转到这个自动生成的stub中,完成必要的操作,再把控制权交给`sgx_ecall`函数,`sgx_ecall`负责完成状态切换到SGX模式,进入Enclave。进入Enclave之后会先来到`Enclave_t.c`中实现的`bridge function`的另一端,进行另一些参数包装之后最后才会执行到Enclave函数的实现。 101 | 102 | 以上这些过程虽然是程序员“不可见”的,但是他们是理解SGX的关键之一。Intel甚至在其[主页](https://software.intel.com/en-us/sgx-sdk/documentation)上公开了两份文档[[1]](https://software.intel.com/sites/default/files/managed/e1/ec/SGX_SDK_Developer_Guidance-CVE-2017-5753.pdf)[[2]](https://software.intel.com/sites/default/files/managed/e1/ec/180309_SGX_SDK_Developer_Guidance_Edger8r.pdf)来“教导”开发人员如何正确的做Bound Check以防止产生spectre风险。然而鲜有开发者会来读这两份冗长且无聊的文档——“知道太多的细节对于我们来说是负担”这句话根深蒂固的隐藏在开发人员的心里但是,这对构造一个Intel SGX程序来说是**非常致命的**。我将这两份文档潦草的翻译成了中文[[3]](02.md)[[4]](03.md),希望对于看到本文的开发者有所帮助。 103 | 104 | 简单读过`hello-rust/enclave/Enclave.edl`之后,并不难理解在本例中唯一一个用户定义的ECALL函数是`say_something`,其参数包括一个`uint8_t *`的指针和一个长度`len`。在EDL里利用`size=len`来告诉SGX**要把多少个字节的参数拷贝到Enclave里**。达到的效果是:在Enclave里的`say_something`看来,这个指针是**指向Enclave**内存的,而不是指向untrusted部分的内存。所以,ECall会把这样的参数**连续的按字节拷贝**到Enclave的加密内存中,因此需要个长度。指针型参数只有一个例外:危险的`[usercheck]`标签修饰的参数,这个以后再说。 105 | 106 | 在`hello-rust/app/src/main.rs`的128-145行,是一个完整的调用ECALL的例子。其中可以看到,给ECALL传递参数的时候是包括两个“隐参数”的,即`enclave id`和"返回值所存的地址`&ret`。并且这个ECALL会具有另外一个返回值`result`。这里`result`代表的含义是:ECALL执行是否成功。`ret`代表的含义是:`ECALL函数的返回值`。读者现在可以区分这两个值的区别吗?哪个值是Intel SGX定义的返回值,哪个是`say_something`的返回值呢? 107 | 108 | `main.rs`的135行展示了如何将一个Rust string转化成一个`char *`类型的C string并传给Enclave。 109 | 110 | 再来看Enclave部分。Enclave部分的`Cargo.toml`声明了这是一个`staticlib`,也就是说,Enclave部分最后会被编译成一个`.a`文件。这个`.a`文件会和Intel提供的一系列`sgx_tstdc.a`等库文件链接在一起形成`enclave.so`,再经由`sgx_sign`工具配合`Enclave.config.xml`配置文件、签名私钥一起做签名和属性刻画,最后生成`enclave.signed.so`,才是Enclave的完全体。在dependency一栏,写了两个依赖项`sgx_types`和`sgx_tstd`。前者不用细说,后者就是我们移植的标准库,包含了诸多基本功能的实现。 111 | 112 | `enclave/src/lib.rs`即是Enclave的源代码了。有C/C++基础的读者读起来应该没有什么困难。唯一值得注意的是71行: 113 | 114 | ``` 115 | 71 // Ocall to normal world for output 116 | 72 println!("{}", &hello_string); 117 | ``` 118 | 119 | 这里要`print`出去。众所周知,print东西到屏幕上是需要做syscall的,而SGX内不可以做syscall,所以这里一定要做OCALL。但是在该例子EDL里没有直接出现OCALL打印函数。这是因为我们在实现`println!`的过程中加入了内置的OCALL,并定义了对应的EDL,并且在`Enclave.edl`中做了import。 120 | 121 | ``` 122 | 32 enclave { 123 | 33 from "sgx_tstd.edl" import *; 124 | 34 from "sgx_stdio.edl" import *; 125 | 35 from "sgx_backtrace.edl" import *; 126 | 36 from "sgx_tstdc.edl" import *; 127 | ``` 128 | 129 | 如果有libc基础的同学一定知道`printf`实现的复杂性。从`printf`到最后`write`调用中间的过程是非常复杂的,在Enclave里也是如此。我们一定要在这个超长的调用过程中的某一点做切割,将控制流从Enclave内转到Enclave外,在untrusted部分完成write。那在哪里切割比较好呢?这就是所谓的**partition**了。 130 | 131 | **Partition, partition, partition** 132 | 133 | 在SGX编程中,**partition意味着划分可信/不可信的边界**。在Enclave内的代码一般被认为是被信任的代码,在Enclave外的代码一般被认为是不可以被信任的代码。这里一个良好的设计原则是:无论Enclave外的代码如何执行,都不会泄露Enclave内的数据,破坏Enclave内的执行逻辑。因此partition是一个“业务逻辑”层面的事。对于一个业务来说,partition可以有无数种方式,但不恰当的partition会导致SGX程序“似乎被保护了”但是并**不可信**。 134 | 135 | 让我们拿[Scone-python](https://sconedocs.github.io/Python/)做例子。Scone(和Graphene SGX)是一类试图将”任意程序“跑在SGX内的解决方案。他们甚至可以直接跑python解释器。看上去很美。但是在SGX的威胁模型下,只有**Enclave内**是可信的,这导致,在Enclave内执行的python解释器在执行到`import numpy`的时候,会从磁盘中读取`numpy`的python文件并进行解释,而读进来的文件是**不可信**的!这时相当于:在可信环境中执行了**不可信**的代码,然而——用户依然认为它是可信的!这是一个典型的错误可信计算基的实例。攻击者可以简单的替换磁盘中的`numpy`,下次用户再次`import numpy`的时候,就会使用植入了恶意逻辑(比如发送secret)的`numpy`在Enclave内执行,导致秘密泄露。 136 | 137 | 回到我们的例子中。我们的`prinln!`是在哪里切割的呢?这参考了Rust自身的抽象设计。感兴趣的同学可以查看`sgx_tstd`的源代码。相信你一定会有所发现。 138 | 139 | Enclave代码`lib.rs`的最后就是返回一个`SGX_SUCCESS`了,很像python是不是? 140 | 141 | 有点累了,今天先说到这。希望各位能读一下[[3]](02.md)和[[4]](03.md),增加对SGX安全的理解。要重申的是,编写一个Enclave和编写普通app完全不同,需要考虑所有**直接输入和隐式输入**是否可信,而不是简单的——使用它们。 142 | 143 | -------------------------------------------------------------------------------- /02.md: -------------------------------------------------------------------------------- 1 | # Intel® Software Guard Extensions (SGX) SW Development Guidance for Potential Bounds Check Bypass (CVE-2017-5753) Side Channel Exploits 参考中译文 2 | 3 | White Paper/Revision 1.1/March 2018 4 | 版权属于Intel 5 | 6 | ## Background 7 | 8 | 2018年1月3日,Google Project Zero的安全团队发现了三个变种的side channel攻击。Intel Security Advisory记录了这三个攻击。Spectre指变种1和变种2,Meltdown指的是变种3。 9 | 10 | Intel SGX程序也受到这个攻击的影响。Intel发布的指南中说明了通过软件方式缓解这些攻击也是重要的组成部分。 11 | 12 | 这份文档中我们解释Intel SGX SDK为了缓解这些攻击所做的改动,并且提供了一份安全编程指南,帮助开发人员写出安全的代码。 13 | 14 | ## Intel® SGX SDK Changes 15 | 16 | 为了修复Bound Check Bypass [CVE-2017-5753], Intel SGX SDK做了相应更新。做法是通过插入LFENCE指令,在该情况下停止speculative execution。SDK的修改如下表: 17 | 18 | 19 | |SDK修改 | 防止的Bypass | 受影响的SDK组件 | 20 | | --- | --- | --- | 21 | |在查ECALL function table时停止推测执行。| 外界攻击者可以控制ECALL function table的index。这个index的检查可以被bypass。 | tRTS | 22 | |在enclave操作secret memory(即使不被考虑为secret)时停止推测执行。|使用Intel SGX SDK生成的enclave中,某些经由指针传入enclave的缓冲区在传入时被检查是否不在enclave地址空间中。这些检查可以被bypass。|Edger8r和tRTS| 23 | |在第一次enclave call时停止推测执行。第一次enclave call是enclave装载的一部分,并不执行任何Edger8r生成的代码。|第一次enclave call传递一个指针,这个指针会被作为enclave外的指针进行处理。对于这个指针的检查可以被bypass。|tRTS| 24 | |对于可能溢出sealed data数据结构的操作,停止推测执行。|sgx_sealed_data_t数据结构包括用于计算指针的长度域。对于这些值的检查可以被bypass。|tSeal| 25 | |在trusted key exchange库中停止推测执行。使用这个库会给依赖于这个库的enclave增加额外的ECALL: sgx_ra_get_msg3_trusted,这个ECALL包括一个user_check指针。|对于user_check指针的检查(检查这个buffer是否位于enclave外)的操作可以被bypass。由于这是一个user_check指针,所有其他的SDK更新对此无效。|tkey_exchange| 26 | |在trusted key exchange库中停止推测执行。使用这个库会给依赖于这个库的enclave增加额外的ECALL。这些ECALL引入了一个context参数,这个参数会被用作session数组的index。|外界攻击者可以控制这个context参数。对于这个context参数的检查(是否越界访问)可以被bypass。|tkey_exchange| 27 | 28 | ## Intel® SGX Developer Guidance 29 | 30 | 为了利用本次SDK更新,开发人员应该使用新版SDK重新编译生成SGX enclave。这里的新版SDK指的是:Intel® SGX SDK for Windows* OS Version 1.9.106 和 Intel® SGX SDK for Linux OS Version 2.1.102。根据Intel SGX开发手册,开发人员可以选择自增SGX enclave的ISVSVN,以体现这次安全升级。因此,在某些情况下,在需要引用到ISVSVN建立信任和provision secret时,需要更新对应的ISVSVN,以对应于这次安全更新。 31 | 32 | 但是,使用更新后的SDK重新编译很可能不解决所有问题。并不是所有的Bounds Check Bypass攻击都可以被重新编译直接缓解。所有攻击者可干涉的内存读取操作都应该被人工审计分析。这意味着enclave开发人员需要仔细分析SDK没有处理过的enclave输入路径。以下章节描述了几种enclave输入数据的方法,以及每种方法开发人员应采取的缓解措施。 33 | 34 | ### Enclave输入 35 | 36 | 以下三种Enclave输入模式可以导致被攻击的风险: 37 | 38 | * 输入被解释为地址/指针 39 | * 输入值参与地址计算 40 | * 输入指向的内容(递归包含)被解释为地址,或参与地址运算 41 | 42 | 产生攻击的原因是:攻击者可以篡改Enclave的输入参数,使其直接或间接计算生成的地址指向所泄露的secret。此时,由于输入会经由一个“输入指针是否在enclave外”的判断,于是符合了spectre攻击的条件,触发推测执行,导致side channel attack风险。 43 | 44 | 即使enclave所对应的EDL文件并没有声明任何此类参数,一个enclave仍然会包含此类参数。这是因为在编译过程中,Edger8r会为把每个ECALL的参数包装成一个“Marshaling structure”,并使用一个指针指向这个结构体并作为参数进行传递。并且,Edger8r所生成代码和tRTS的部分代码一同维护一个“ecall table”的数据结构,用于将开发者定义的ECALL转化为一个indirect call。针对这个地址转换进行的攻击,以及上述“Marshaling structure”指针的攻击,在本次SDK更新中得到缓解。 45 | 46 | 此外,更新后的SDK在EDL文件中定义的地址类参数在处理中自动插入缓解指令,只要满足以下条件: 47 | 48 | * EDL文件中此参数被定义为指针类型 49 | * 没有使用user_check关键字或者sizefunc属性来修饰此参数。注:某一个"sizefunc input"是否可以被exploit,依赖于这个特定的sizefunc函数。这个特定的sizefunc函数是开发者自己编写的。 50 | * 这个参数的“指针性”(原文是“pointer-ness”,意为这个参数是否本质上具有指针的语义)没有被typedef所掩盖。注:这种情况下,EDL文件中的"isptr"属性可以被用于标识这个参数具有指针性,会被当做指针对待。 51 | 52 | 然而,即使一个输入的指针参数符合了以上标准,一旦这个指针所指向的数据结构包含了任何一个可以用来计算地址/指针的域,那么,开发者有责任去分析这个enclave代码中所有使用这个“嵌套指针”数据结构的代码,以去除安全隐患。更新后的SDK对此类情况没有帮助,只有一个情况除外:一个使用了sealing库的enclave会引入一个指针,指向seal过的blob,作为输入。sealing库的代码会将这个blob解析为一个含有offset的数据结构。在此次更新的SDK中加入了对应的缓解,以防止产生危险。 53 | 54 | 在真实世界中,处理可变长参数是开发人员经常遇到的情况,并且需要开发人员仔细分析。可变长参数的头部通常包含一个长度域,常见的做法是借用这个传入的长度作为边界来读取参数的值。但是这样的实现就会导致mis-predict,进一步导致推测执行机制执行本不该执行的代码。 55 | 56 | ### 例子 57 | 58 | ``` 59 | // EDL定义 60 | public uint32_t enclave_function([in, size = alloc_size] tlv_t* varlen_input, 61 | uint32_t alloc_size); 62 | // 头文件定义 63 | typedef struct { 64 | unsigned type; 65 | unsigned length; 66 | void* payload; 67 | } tlv_t; 68 | ``` 69 | 70 | 上图所示的EDL文件中,SDK代码会检查一段长度为alloc_size的内存是否位于enclave之外,本次更新后的SDK加入了缓解指令,避免危险的side channel attack。然而,开发者编写的代码中仍然可能具有根据tlv_t结构中length域对payload进行访问的代码。这样的内存访问所产生的side channel attack风险是不会被SDK的更新所缓解的。例如,开发者往往会先比较varlen_input.length和alloc_size的大小关系,再做相应的数据读取操作。这个比较操作就可以被攻击者利用,导致预测器的mis-predict,进而造成spectre攻击风险。开发人员有责任在确定长度合法之后插入一条LFENCE指令,来防止推测执行机制访问到真实数据。如果性能允许,开发者可以简单的在每个分支之后插入LFENCE指令,避免繁重的路径分析工作,如: 71 | 72 | ``` 73 | // make sure payload is outside enclave 74 | If (varlen_input.length > alloc_size) { 75 | // error code 76 | ... } 77 | Else { 78 | _mm_lfence(); 79 | // 80 | // valid length path 81 | // 82 | ... 83 | } 84 | ``` 85 | 86 | 其中_mm_lfence是Intel编译器中LFENCE指令对应的函数。其他的编译器使用__builtin_ia32_lfence。 87 | 88 | 89 | ### EDL属性中的user_check 90 | 91 | EDL属性中的user_check属性告诉SDK其修饰的指针不需要被检查(检查缓冲区是否位于enclave地址空间内)。SDK代码会把user_check修饰的参数作为整数对待。因此一旦开发者使用user_check对指针参数进行修饰,那么开发者需要负责分析所有直接、间接使用这个指针参数的enclave代码。如果参数是user_check指针并且指向enclave外的内存地址,那么以下的代码模式会直接导致side channel攻击风险。在此例子中加入LFENCE指令来缓解这种风险,其导致的性能下降是可以接受的。 92 | 93 | ``` 94 | // EDL 95 | public uint32_t enclave_function([user_check]const uint8_t* user_check_input, 96 | uint32_t user_check_size); 97 | 98 | uint32_t enclave_function(const uint8_t* user_check_input, 99 | uint32_t user_check_size) 100 | { 101 | ... 102 | // 103 | // make sure input buffer is outside enclave 104 | // 105 | int SGXAPI sgx_is_outside_enclave(const void *addr, size_t 106 | size); 107 | if (!sgx_is_outside_enclave(user_check_input, 108 | user_check_size)) { 109 | // error code 110 | ... 111 | } 112 | else { 113 | _mm_lfence(); 114 | ... 115 | } 116 | ... 117 | } 118 | ``` 119 | 120 | sgx_is_outside_enclave 是SDK默认提供的函数,其意自明。这个函数检查一个由起始地址和长度定义的buffer是否完全处于enclave地址空间之外。 121 | 122 | 如果一个user_check输入本身指向理应处于enclave地址空间内的数据结构,攻击者可以篡改这个指针,使其指向enclave内的错误位置。开发人员应当更加仔细的检查此类输入参数。 123 | 124 | ### Table/Array Indexing 125 | 126 | 首先来看以下这段漏洞代码: 127 | 128 | ``` 129 | void victim_function(size_t x) { 130 | if (x < array1_size) { 131 | temp &= array2[array1[x] * 512]; 132 | } 133 | } 134 | ``` 135 | 136 | 如果victim_function是EDL指定的受信任的enclave函数,那么SDK的代码完全无法缓解这段代码中含有的side channel attack风险,因为SDK并不知道输入会被用于参与地址计算。开发者应当插入对应的检查代码来缓解风险,例如: 137 | 138 | ``` 139 | void victim_function(size_t x) { 140 | if (x < array1_size) { 141 | _mm_lfence(); 142 | temp &= array2[array1[x] * 512]; 143 | } 144 | } 145 | ``` 146 | 147 | ### sizefunc 148 | 149 | EDL属性中的sizefunc使得在EDL进行函数定义时,开发人员可以指定一个函数,这个函数用于计算函数参数的长度。这个sizefunc所修饰的函数是运行在enclave内的。但由于其负责计算输入的长度,因此在进行常规的“输入参数是否位于enclave之外”这一检查之前,输入参数就会被访问到(例如计算一个包含长度域的不定长buffer的sizefunc函数)。这个过程容易引起side channel攻击风险。因此sizefunc函数应当被马上从EDL定义中移除。开发人员应该使用更安全的size属性来确定输入参数的长度,从而保证在读取输入参数之前就使得index不会越界。 150 | 151 | ``` 152 | size_t tcalc_size(const tlv_t* varlen_input, size_t maxlen) 153 | { 154 | size_t len = sizeof(tlv_t); 155 | if (len > maxlen) 156 | { 157 | len = 0; 158 | } else { 159 | _mm_lfence(); //fence after check (CVE-2017-5753) 160 | len = varlen_input->length; 161 | if (len > maxlen) 162 | { 163 | len = 0; 164 | } 165 | } 166 | _mm_lfence(); //fence after check (CVE-2017-5753) 167 | return len; 168 | } 169 | void ecall_no_sizefunc(tlv_t* varlen_input, size_t len) 170 | { 171 | // make sure code won't go past input 172 | // before processing 173 | if (tcalc_size(varlen_input, len) == 0) 174 | { 175 | //record the error 176 | return; 177 | } 178 | //tcalc_size performs fence 179 | //process varlen_input 180 | return; 181 | } 182 | ``` 183 | 184 | ## Compiler Support 185 | 186 | Microsoft 在2018年1月15日发布了一篇blog,描述了一个新的编译器开关/Qspectre,用于缓解Spectre。Intel SGX开发者可以使用提供了这个开关的编译器重新生成SGX enclave。 187 | 188 | **译者补充**:LLVM 6.0 加入了 -mretpoline 开关用于抵抗 type-2 的 spectre 攻击。但是目前只能通过 clang 前端启用。 189 | 190 | -------------------------------------------------------------------------------- /03.md: -------------------------------------------------------------------------------- 1 | # Intel® Software Guard Extensions (SGX) SW Development Guidance for Potential Edger8r Generated Code Side Channel Exploits 参考译文 2 | 3 | White Paper, Revision 1.0, March 2018 4 | 5 | ## Background 6 | 7 | 2018年2月16日,来自Catholic University of Leuven(KU Leuven)的一个安全研究团队告知Intel由于Intel SGX SDK中Edger8r工具引起的安全问题。这个问题会导致Edger8r工具产生的SGX代码含有严重的side channel安全隐患,使得enclave内的数据被外部攻击者窃取。 8 | 9 | Intel SGX程序会使用Edger8r工具来产生边界代码。这些边界代码由一系列proxy函数和bridge函数组成。proxy函数是程序调用ECALL函数时,application一侧的接口函数。bridge函数是ECALL进来之后所执行的函数,是enclave一侧的接口函数,,这个bridge函数最后会调用到ECALL真正所指向的函数。SGX开发者使用EDL文件作为输入,交给Edger8r工具,来生成这些函数。这份EDL文件需要符合Edger8r所规定的语法。这个语法中的两个关键属性:string和sizefunc所产生的代码容易受到此次side channel attack的威胁。开发者一旦使用了这两个关键字来修饰函数参数,那么产生的SGX程序就基本含有spectre漏洞。开发人员应当遵循本文档中提出的建议来最小化潜在的威胁。 10 | 11 | 这份文档中我们介绍为了缓解spectre,对EDL语法和相应的库以及工具做出的改动。 12 | 13 | ## Intel SGX SDK Changes 14 | 15 | 为了解决Edger8r的问题,SDK进行了更新。下表展示了SGX SDK(Windows* OS Version 1.9.106 和 Intel® SGX SDK for Linux OS Version 2.1.102)的改动: 16 | 17 | |SDK更改|影响|受影响的SDK组件| 18 | |---|---|---| 19 | |修改了'string'属性的实现。现在'string'修饰的参数的长度在untrusted代码中进行。|使用了'string'属性的代码需要重新使用Edger8r编译,并重新生成整个项目。|Edger8r Tool| 20 | |移除了'sizefunc'属性|如果项目中使用了'sizefunc'来修饰参数,那么开发人员应该修改函数定义和实现,使用‘size’来指定一个固定长度的输入。此外开发人员应当重新编译和生成EDL文件以及整个项目。|Edger8r Tool| 21 | |在enclave操作secret memory(即使不被考虑为secret)时停止推测执行。(参考上文)|所有在EDL文件中传递了指针的项目应当使用更新后的Edger8r重新编译,并重新生成整个项目。|Edger8r Tool| 22 | |更新了SDK样例代码,移除了所有sizefunc的使用|展示了一种不使用'sizefunc'传递参数的办法|SDK样例代码| 23 | 24 | 25 | 26 | ## Intel SGX Developer Guidance 27 | 28 | 为了利用更新后的SDK,开发人员应使用新版SDK重新编译他们的项目。根据Intel SGX开发手册,开发人员可以选择自增SGX enclave的ISVSVN,以体现这次安全升级。因此,在某些情况下,在需要引用到ISVSVN建立信任和provision secret时,需要更新对应的ISVSVN,以对应于这次安全升级。 29 | 30 | 如果开发人员使用了sizefunc属性,那么直接重新编译项目是不够的必须手工修复代码,去掉所有的sizefunc属性。这篇文章提供了一种消除sizefunc的参考实现。 31 | 32 | 接下来的章节描述了Edger8r工具产生代码的变动,以及介绍了一种去掉sizefunc的方法。 33 | 34 | ### Edger8r string Attribute 35 | 36 | ECALL函数参数中的'string'属性可能会导致漏洞。使用'string'修饰函数参数会使得自动生成的enclave代码中包括一个strlen函数的调用。strlen返回的长度用于定义参数的buffer长度。 37 | 38 | 如果'string'修饰的参数指向一个enclave内的地址,那么strlen将直接计算从这个enclave内地址开始的字符串的长度。同时自动生成的代码部分会检查这个输入buffer是否位于enclave内。 39 | 40 | 攻击者可以在enclave外构造恶意输入,使得'string'修饰的参数指向精心构造的位置,从而触发spectre攻击,使用timing attack泄露enclave内的数据。 41 | 42 | ### Example - Current Edger8r (未更新的) 43 | 44 | ``` 45 | /* EDL 46 | * [string]: 47 | * the attribute tells Edger8r 'str' is NULL terminated string, 48 | * so strlen will be used to count the length of buffer pointed 49 | * by 'str'. */ 50 | public void ecall_pointer_string([in, string] char *str) 51 | 52 | typedef struct ms_ecall_pointer_string_t { 53 | char* ms_str; 54 | } ms_ecall_pointer_string_t; 55 | ``` 56 | 57 | 当上图所述的EDL代码被Edger8r工具处理时,Edger8r工具会自动生成一个"marshalling structure":ms_ecall_pointer_string_t。这个结构包含了一个指针ms_str指向输入参数(str)。 58 | 59 | ``` 60 | sgx_status_t ecall_pointer_string(sgx_enclave_id_t eid, char* str) 61 | { 62 | sgx_status_t status; 63 | ms_ecall_pointer_string_t ms; 64 | ms.ms_str = str; 65 | status = sgx_ecall(eid, 12, &ocall_table_Enclave, &ms); 66 | return status; 67 | } 68 | ``` 69 | 70 | 上图是对应的Edger8r产生的proxy函数。这里参数包括了一个'string'修饰的参数。在这个函数中,用户提供的指针被填充到marshalling structure对应的域里。这个操作在untrusted部分完成。 71 | 72 | ``` 73 | #define CHECK_REF_POINTER(ptr, siz) do { \ 74 | if (!(ptr) || ! sgx_is_outside_enclave((ptr), (siz))) \ 75 | return SGX_ERROR_INVALID_PARAMETER;\ 76 | } while (0) 77 | 78 | #define CHECK_UNIQUE_POINTER(ptr, siz) do { \ 79 | if ((ptr) && ! sgx_is_outside_enclave((ptr), (siz))) \ 80 | return SGX_ERROR_INVALID_PARAMETER;\ 81 | } while (0) 82 | 83 | static sgx_status_t SGX_CDECL sgx_ecall_pointer_string(void* pms) 84 | { 85 | CHECK_REF_POINTER(pms, sizeof(ms_ecall_pointer_string_t)); 86 | ms_ecall_pointer_string_t* ms = 87 | SGX_CAST(ms_ecall_pointer_string_t*, pms); 88 | sgx_status_t status = SGX_SUCCESS; 89 | char* _tmp_str = ms->ms_str; 90 | size_t _len_str = _tmp_str ? strlen(_tmp_str) + 1 : 0; 91 | char* _in_str = NULL; 92 | CHECK_UNIQUE_POINTER(_tmp_str, _len_str); 93 | // fence after pointer checks 94 | _mm_lfence(); 95 | if (_tmp_str != NULL && _len_str != 0) { 96 | _in_str = (char*)malloc(_len_str); 97 | if (_in_str == NULL) { 98 | status = SGX_ERROR_OUT_OF_MEMORY; 99 | goto err; 100 | } 101 | memcpy(_in_str, _tmp_str, _len_str); 102 | _in_str[_len_str - 1] = '\0'; 103 | } 104 | ecall_pointer_string(_in_str); 105 | err: 106 | if (_in_str) free(_in_str); 107 | return status; 108 | } 109 | ``` 110 | 111 | 上图是Edger8r产生的bridge函数。可以看到,strlen操作在enclave里面完成。 112 | 113 | ### Example - Updated Edger8r 114 | 115 | Edger8r工具的更新修改了marshalling structure的定义,在上例中,更新后的Edger8r会产生如下的Marshalling structure: 116 | 117 | ``` 118 | typedef struct ms_ecall_pointer_string_t { 119 | char* ms_str; 120 | size_t ms_len_str; 121 | } ms_ecall_pointer_string_t; 122 | ``` 123 | 124 | 这里多了一个成员ms_len_str,用于指明这个字符串的长度。 125 | 126 | ``` 127 | sgx_status_t ecall_pointer_string(sgx_enclave_id_t eid, char* str) { 128 | sgx_status_t status; 129 | ms_ecall_pointer_string_t ms; 130 | ms.ms_str = str; 131 | ms.ms_len_str = strlen(str) + 1; 132 | status = sgx_ecall(eid, 12, &ocall_table_Enclave, &ms); 133 | return status; 134 | } 135 | ``` 136 | 137 | 上图是更新后的Edger8r工具产生的bridge函数,运行在untrusted部分。可以看到,string的长度是由strlen函数在untrusted部分计算得到的,不涉及到enclave的内存。 138 | 139 | ``` 140 | #define CHECK_REF_POINTER(ptr, siz) do { \ 141 | if (!(ptr) || ! sgx_is_outside_enclave((ptr), (siz))) \ 142 | return SGX_ERROR_INVALID_PARAMETER;\ 143 | } while (0) 144 | 145 | #define CHECK_UNIQUE_POINTER(ptr, siz) do { \ 146 | if ((ptr) && ! sgx_is_outside_enclave((ptr), (siz))) \ 147 | return SGX_ERROR_INVALID_PARAMETER;\ 148 | } while (0) 149 | 150 | static sgx_status_t SGX_CDECL sgx_ecall_pointer_string(void* pms) 151 | { 152 | CHECK_REF_POINTER(pms, sizeof(ms_ecall_pointer_string_t)); 153 | // fence after pointer checks 154 | sgx_lfence(); 155 | ms_ecall_pointer_string_t* ms = 156 | SGX_CAST(ms_ecall_pointer_string_t*, pms); 157 | sgx_status_t status = SGX_SUCCESS; 158 | char* _tmp_str = ms->ms_str; 159 | size_t _len_str = ms->ms_len_str; 160 | char* _in_str = NULL; 161 | CHECK_UNIQUE_POINTER(_tmp_str, _len_str); 162 | // fence after pointer checks 163 | sgx_lfence(); 164 | if (_tmp_str != NULL) { 165 | _in_str = (char*)malloc(_len_str); 166 | if (_in_str == NULL) { 167 | status = SGX_ERROR_OUT_OF_MEMORY; 168 | goto err; 169 | } 170 | memcpy(_in_str, _tmp_str, _len_str); 171 | _in_str[_len_str - 1] = '\0'; 172 | } 173 | ecall_pointer_string(_in_str); 174 | err: 175 | if (_in_str) free(_in_str); 176 | return status; 177 | } 178 | ``` 179 | 180 | 上图是更新后的Edger8r生成的proxy函数。可以看到_len_str直接使用了ms->ms_len_str的值,而不是在enclave内调用strlen计算得到。并且更新后的Edger8r插入了额外的lfence指令。 181 | 182 | ### Edger8r sizefunc Attribute 183 | 184 | EDL语法中的sizefunc属性用于允许开发者对一个参数给定一个函数用于计算参数的长度。这个长度计算函数在enclave内执行,并在判断参数是否位于enclave内之前执行。因此,这个用于计算参数长度的函数会受到spectre攻击的威胁。所以,在这次更新中sizefunc属性被移除。为了替换sizefunc,开发者应使用size属性来指定参数的长度,并且开发人员必须保证:在enclave内处理输入参数之前,对输入参数的访问不会越界(看下文例子),并且在处理输入参数过程中访问也不会越界。 185 | 186 | ### Example - Current Edger8r using Sizefunc (有问题的sizefunc) 187 | 188 | 下图是一个sizefunc的例子 189 | 190 | ``` 191 | typedef struct _tlv_t { 192 | uint32_t buf_len; 193 | uint8_t buf[]; 194 | } tlv_t; 195 | /* 196 | * [sizefunc]: 197 | * the attribute tells Edger8r that calc_size can be used to 198 | * calculate the size of varlen_input 199 | */ 200 | public void ecall_sizefunc([in, sizefunc = calc_size] tlv_t* varlen_input); 201 | 202 | size_t calc_size(const tlv_t* varlen_input) { 203 | return ( sizeof(tlv_t) + varlen_input->buf_len ); 204 | } 205 | void ecall_sizefunc(tlv_t* varlen_input){ 206 | //process varlen_input 207 | return; 208 | } 209 | ``` 210 | 211 | 这个例子中calc_size用于计算varlen_input参数的长度。Edger8r会自动生成一个Bridge Function(ECALL进来之后执行的第一个函数),用于执行calc_size来计算varlen_input的长度。由于varlen_input这个参数所指向的数据不一定位于enclave外,所以造成calc_size在得知varlen_input是否位于enclave外之前就被执行,具有side channel的风险。事实上,clac_size被执行了两次,一次作用于enclave外的Marshalling structure ms->ms_varlen_input,另一次作用于enclave内的_in_varlen_input。这么做是为了确认传入的参数长度确实等于第一次在enclave外计算的长度。 212 | 213 | 这里就暴露了一个sizefunc设计上的难题。为了判定传入参数是否位于enclave外,bridge routine必须得知参数的长度。但是这个参数长度的计算只能通过sizefunc进行。 214 | 215 | ``` 216 | static sgx_status_t SGX_CDECL sgx_ecall_sizefunc(void* pms) { 217 | CHECK_REF_POINTER(pms, sizeof(ms_ecall_sizefunc_t)); 218 | ms_ecall_sizefunc_t* ms = SGX_CAST(ms_ecall_sizefunc_t*, pms); 219 | sgx_status_t status = SGX_SUCCESS; 220 | tlv_t* _tmp_varlen_input = ms->ms_varlen_input; 221 | size_t _len_varlen_input = ((_tmp_varlen_input) ? calc_size(_tmp_varlen_input) : 0); 222 | tlv_t* _in_varlen_input = NULL; 223 | CHECK_UNIQUE_POINTER(_tmp_varlen_input, _len_varlen_input); 224 | // fence after pointer checks 225 | _mm_lfence(); 226 | if (_tmp_varlen_input != NULL && _len_varlen_input != 0) { 227 | _in_varlen_input = (tlv_t*)malloc(_len_varlen_input); 228 | if (_in_varlen_input == NULL) { 229 | status = SGX_ERROR_OUT_OF_MEMORY; 230 | goto err; 231 | } 232 | memcpy(_in_varlen_input, _tmp_varlen_input, _len_varlen_input); 233 | /* check whether the pointer is modified. */ 234 | if (calc_size(_in_varlen_input) != _len_varlen_input) { 235 | status = SGX_ERROR_INVALID_PARAMETER; 236 | goto err; 237 | } 238 | // fence after final sizefunc check 239 | _mm_lfence(); 240 | } 241 | ecall_sizefunc(_in_varlen_input); 242 | err: 243 | if (_in_varlen_input) free(_in_varlen_input); 244 | return status; 245 | } 246 | ``` 247 | 248 | 以上这个解法与'string'属性的解法非常相似,都需要在untrusted一侧的proxy function里计算参数长度,并且在Marshalling structure里增加一个额外的长度域用于传递这个长度。但是这个解法带来了另一个问题:需要一个enclave内用于计算参数长度的函数,来确保参数传递进enclave之后长度不变。这第二次检查是用于确认真正传入的参数没有超过长度域所指定的长度。 249 | 250 | 因此这个解决方案要求开发人员编写: 251 | Marshalling structure结构体,包含一个额外的长度域。 252 | 一个untrusted sizefunc用于计算参数长度 253 | 一个trusted sizefunc用于验证参数传递的正确性 254 | 255 | 但是,sizefunc的本来目的是为了方便开发人员编写ECALL函数,自动产生Marshalling structure。然而现在看来,由于安全起见,开发人员不得不增加工作量以确保安全性。 256 | 257 | 因此,在更新后的Edger8r中移除了sizefunc属性。开发人员需要使用size属性来传递参数。 258 | 259 | ### Example - Implementation without sizefunc 260 | 261 | size属性可以用于解决4.2.1节中的问题。如下图例子。 262 | 263 | ``` 264 | /* 265 | * [size]: 266 | * the attribute tells Edger8r that len is the size of 267 | * varlen_input 268 | */ 269 | public void ecall_no_sizefunc([in, size = len] tlv_t* varlen_input, size_t len); 270 | 271 | /* 272 | * ucalc_size: 273 | * calculates the size of a tlv_t structure 274 | */ 275 | size_t ucalc_size(const tlv_t* varlen_input) { 276 | return (sizeof(tlv_t) + varlen_input->buf_len); 277 | } 278 | 279 | int SGX_CDECL main(int argc, char *argv[]) 280 | { 281 | tlv_t varlen_struct; 282 | ... 283 | ret = ecall_no_sizefunc(global_eid, 284 | &varlen_struct, 285 | ucalc_size(&varlen_struct)); 286 | ... 287 | } 288 | ``` 289 | 290 | Edger8r会为这个ecall_no_sizefunc生成如下的Bridge function。这个函数并不调用任何函数来动态确定varlen_input的长度,而是使用Marshalling structure里的长度域作为输入参数的长度,用于判定varlen_input是否位于enclave之外。在确定varlen_input位于enclave之外后,再将其拷贝到enclave内的buffer中。 291 | 292 | ``` 293 | static sgx_status_t SGX_CDECL sgx_ecall_no_sizefunc(void* pms) { 294 | CHECK_REF_POINTER(pms, sizeof(ms_ecall_no_sizefunc_t)); 295 | // fence after pointer checks 296 | sgx_lfence(); 297 | ms_ecall_no_sizefunc_t* ms = SGX_CAST(ms_ecall_no_sizefunc_t*, pms); 298 | sgx_status_t status = SGX_SUCCESS; 299 | tlv_t* _tmp_varlen_input = ms->ms_varlen_input; 300 | size_t _tmp_len = ms->ms_len; 301 | size_t _len_varlen_input = _tmp_len; 302 | tlv_t* _in_varlen_input = NULL; 303 | CHECK_UNIQUE_POINTER(_tmp_varlen_input, _len_varlen_input); 304 | 305 | // fence after pointer checks 306 | _mm_lfence(); 307 | 308 | if (_tmp_varlen_input != NULL && _len_varlen_input != 0) { 309 | _in_varlen_input = (tlv_t*)malloc(_len_varlen_input); 310 | if (_in_varlen_input == NULL) { 311 | status = SGX_ERROR_OUT_OF_MEMORY; 312 | goto err; 313 | } 314 | memcpy(_in_varlen_input, _tmp_varlen_input, _len_varlen_input); 315 | } 316 | ecall_no_sizefunc(_in_varlen_input, _tmp_len); 317 | err: 318 | if (_in_varlen_input) free(_in_varlen_input); 319 | return status; 320 | } 321 | ``` 322 | 323 | 为了确认拷贝进来的varlen_input确实是得到了正确的值,开发人员必须编写一个enclave内的安全长度计算函数,来确认参数传递是正确的,如下图: 324 | 325 | ``` 326 | size_t tcalc_size(const tlv_t* varlen_input, size_t maxlen) { 327 | size_t len = sizeof(tlv_t); 328 | if (len > maxlen) { 329 | len = 0; 330 | } else { 331 | _mm_lfence(); //fence after maxlen check (CVE-2017-5753) 332 | len = sizeof(tlv_t) + varlen_input->buf_len; 333 | if (len > maxlen) { 334 | len = 0; 335 | } 336 | } 337 | _mm_lfence(); //fence after maxlen check (CVE-2017-5753) 338 | return len; 339 | } 340 | 341 | void ecall_no_sizefunc(tlv_t* varlen_input, size_t len) { 342 | if (tcalc_size(varlen_input, len) == 0) { 343 | //record the error 344 | return; 345 | } 346 | //tcalc_size has already performed the fence 347 | //process varlen_input 348 | return; 349 | } 350 | ``` 351 | 352 | 353 | -------------------------------------------------------------------------------- /04.md: -------------------------------------------------------------------------------- 1 | # 移植 2 | 3 | 这一篇主要讨论如何把 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++ 的巨大优势:可移植性超强。 4 | 5 | 不过在讨论移植之前,先看一个极端例子: 6 | 7 | ``` 8 | if f.attr != 0600 then 9 | print '打死我也不说' 10 | else 11 | print '我的密码是deadbeaf' 12 | ``` 13 | 14 | 这例子以文件属性作为输入,判断其是否满足特定条件。一旦满足则吐出一个秘密。这个例子可能显得有些夸张——谁会用文件属性做输出密码的条件嘛!相比之下,下面这个例子就显得比较现实一些。 15 | 16 | ``` 17 | if envattr["GC_FACTOR"] != empty then 18 | gc_factor = envattr["GC_FACTOR"] 19 | ``` 20 | 21 | 这个逻辑看上去就合理多了是不是。通过读取环境变量的值来设定内存回收的参数,并且事实上 [rpython](https://github.com/mozillazg/pypy/blob/master/rpython/memory/gc/incminimark.py) 就是这么做的。 22 | 23 | 但是这样做**不适用于** SGX enclave,因为无论是文件属性,还是环境变量,都不在 SGX 的保护范围之内。虽然 Scone 提供了一个所谓的 [CAS](https://sconedocs.github.io/SCONE_CAS/) 机制,试图让 enclave 跑在可信的环境变量设置之下,但是这依旧**破坏了 SGX 的信任模型**:只信任 CPU 和 Intel。攻击者完全可以通过打破 untrusted 部分的逻辑来污染文件属性、环境变量等并不被 SGX 保护的值,来干扰 SGX enclave 的执行逻辑。传统程序里广泛存在这样的逻辑,因此无论如何设计兼容层,使传统程序直接跑在 SGX enclave 的努力都是徒劳,因为他们依赖于**不可信的输入**。 24 | 25 | 这就又回到“可信”的讨论上来。事实上“可信”的概念并没有个特别清晰的定义。翻到了一篇[文献](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 程序,只是“看上去很美”。 26 | 27 | 那如何构造一个可信的 SGX enclave 呢?答案当然是使用我们的 [rust-sgx-sdk](https://github.com/baidu/rust-sgx-sdk) 啦!我们提供了一个定制版的 `sgx_tstd` 用于取代传统的 `std`,把不可信输入(例如`fs`)都移到了 `sgx_tstd::untrusted` 空间下。如果把一个直接使用不可信输入的库移植进来,那么必然会在编译时报错:找不到符号。并且默认不打开 `net` `time` 等 feature。如果要使用这些不可信输入,那么建议开发人员仔细思考并使用 `untrusted` 下的功能,并重新设计这部分逻辑。 28 | 29 | 关于随机数,我们现在提供了 `sgx_rand` 来满足部分需求。许多项目依赖的 `rand` 我还没来得及移植。 30 | 31 | ## Cargo 和 Xargo 32 | 33 | 简单的来说,如果用 cargo 编译,那么就需要手工 clone 代码然后将 crate 处理为 Rust-SGX 适用的形式。如果用 xargo 编译,那么在 crate 没有使用不可信输入(没有使用 `std::{fs,path,env,net,time}` 等)的情况下),直接在 `Cargo.toml` 里引用然后 `XARGO_SGX=1 make` 就可以了。 34 | 35 | 具体来说,如果是 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 环境下,是要手工补上这一行的。具体怎么操作,参见后面的实例。 36 | 37 | 38 | ## 直接使用支持 no_std 的 crate 39 | 40 | 作为最简单的“移植”(其实也不算移植了),利用第三方库支持 `no_std` 的性质,可以在 Rust SGX + cargo 环境下直接引用 crates.io 上的库。这里以我常用的 [itertools](https://github.com/bluss/rust-itertools) 为例,其 `Cargo.toml` 如是说: 41 | 42 | ``` 43 | [features] 44 | default = ["use_std"] 45 | use_std = [] 46 | ``` 47 | 默认是开启了 `use_std` 这个 feature。于是如果我们要支持 cargo 编译 enclave,就需要关掉这个默认打开的 feature,于是在 `Cargo.toml` 里这么引用: 48 | 49 | ``` 50 | [dependencies] 51 | itertools = { version = "0.7.8" , default-features = false, features = []} 52 | ``` 53 | 54 | 然后正常的 `extern crate itertools` 就可以了。 55 | 56 | ## Cargo 环境下的移植 57 | 58 | 用一个递归来展示这个移植过程 59 | 60 | ``` 61 | def 移植(self): 62 | if self支持 no_std then 63 | 不用修改,直接在依赖处配置好 no_std 的 features 64 | return 65 | # 移植依赖项 (忽略dev-dependencies) 66 | for each dep of self.dependencies 67 | 移植 dep 68 | # 移植自身 69 | (1) wget 库代码 && tar xzf 70 | (2) 编辑 Cargo.toml 修改每个依赖项为移植后的依赖项 71 | (3) 编辑 src/lib.rs 添加特定header(见后文) 72 | (4) 编辑每个源文件 添加 use std::prelude::v1::*; 73 | (5) 仔细review每个使用 fs/path/net/time/env 等不可信输入的地方,修正那里的逻辑 74 | (6) 检查每个 platform dependent 的 feature,将其固定为只适用于 linux-x86_64 的逻辑(因为 linux-SGX 就只有这个环境) 75 | (7) 测试 `cargo build` 是否通过 76 | return 77 | ``` 78 | 79 | (1) 以 [http](https://crates.io/crates/http) 为例。首先看到他的 crate name 是 `http`,目前最新的版本是 0.1.8。那么获取其源代码的命令是: 80 | 81 | ``` 82 | wget https://crates.io/api/v1/crates/http/0.1.8/download -O http-0.1.8.crate 83 | ``` 84 | 85 | 这是个 tgz 文件,可以直接用 `tar -xzf` 解压。 86 | 87 | (2) 进入源代码目录后,首先用 `cargo tree` (`cargo install cargo-tree`安装)来看其依赖: 88 | 89 | ``` 90 | http v0.1.8 (file:///tmp/http-0.1.8) 91 | [dependencies] 92 | ├── bytes v0.4.9 93 | │ [dependencies] 94 | │ ├── byteorder v1.2.3 95 | │ └── iovec v0.1.2 96 | │ [dependencies] 97 | │ └── libc v0.2.42 98 | ├── fnv v1.0.6 99 | └── itoa v0.4.2 100 | [dev-dependencies] 101 | ... 102 | ``` 103 | 104 | 于是需要先处理 `iovec v0.1.2`。注意这里他依赖了 `libc`。在 Rust SGX 环境下是**不能使用`libc`**的,因为**没有**(手动狗头)。我们把`libc`里兼容 SGX 的部分提炼出来,放入了 `sgx_trts::libc`。如果这个满足不了需要的话,那么就没有办法了! 105 | 106 | 直接下载 `iovec v0.1.2` 的代码,分析其如何依赖于`libc`: 107 | 108 | ``` 109 | ~/iovec-0.1.2 $ grep -R libc . 110 | ./Cargo.toml:libc = "0.2" 111 | ./src/sys/unix.rs:use libc; 112 | ./src/sys/unix.rs: unsafe fn iovec(&self) -> libc::iovec { 113 | ./src/sys/unix.rs: mem::transmute(libc::iovec { 114 | ./src/sys/unix.rs: mem::transmute(libc::iovec { 115 | ./src/unix.rs:use libc; 116 | ./src/unix.rs:/// Convert a slice of `IoVec` refs to a slice of `libc::iovec`. 117 | ./src/unix.rs:pub fn as_os_slice<'a>(iov: &'a [&IoVec]) -> &'a [libc::iovec] { 118 | ./src/unix.rs:/// Convert a mutable slice of `IoVec` refs to a mutable slice of `libc::iovec`. 119 | ./src/unix.rs:pub fn as_os_slice_mut<'a>(iov: &'a mut [&mut IoVec]) -> &'a mut [libc::iovec] { 120 | ./src/lib.rs:extern crate libc; 121 | ``` 122 | 123 | 看上去`iovec`只依赖于`libc::iovec`这个结构体的定义!那就好说了,`sgx_trts::libc::iovec`这个我们是有的。于是可以如下修改`Cargo.toml`: 124 | 125 | ``` 126 | [dependencies] 127 | sgx_trts = "=1.0.1" 128 | sgx_tstd = "=1.0.1" 129 | ``` 130 | 131 | 这里去掉了关于 `unix``windows` 的feature。并且在整个项目里都要删除所有`windows`下的部分,无条件保留`unix`下的部分,并删除这两个feature。 132 | 133 | (3) 在`lib.rs`里做如下修改: 134 | 135 | ``` 136 | #![no_std] 137 | extern crate sgx_tstd as std; 138 | extern crate sgx_trts; 139 | 140 | mod sys; 141 | use std::{ops, mem}; 142 | pub mod unix; 143 | ``` 144 | 145 | (4) 在`src/unix.rs`里做如下修改: 146 | 147 | ``` 148 | use std::prelude::v1::*; 149 | 150 | use IoVec; 151 | use sgx_trts::libc; 152 | use std::mem; 153 | ``` 154 | 155 | 把`src/sys/mod.rs`改成: 156 | 157 | ``` 158 | mod unix; 159 | 160 | pub use self::unix::{ 161 | IoVec, 162 | MAX_LENGTH, 163 | }; 164 | ``` 165 | 166 | 这里去掉了所有的`unix`和不必要的代码。 167 | 168 | 把`src/sys/unix.rs`改成: 169 | 170 | ``` 171 | use std::prelude::v1::*; 172 | use sgx_trts::libc; 173 | use std::{mem, slice, usize}; 174 | ``` 175 | 176 | 于是看起来就都处理干净了,这时候试试编译: 177 | 178 | ``` 179 | ~/iovec-0.1.2 $ cargo b 180 | Updating registry `https://github.com/rust-lang/crates.io-index` 181 | Downloading sgx_tstd v1.0.1 182 | Downloading sgx_unwind v0.0.2 183 | Downloading sgx_tprotected_fs v1.0.1 184 | Downloading sgx_alloc v1.0.1 185 | Compiling cfg-if v0.1.4 186 | Compiling libc v0.2.42 187 | Compiling sgx_unwind v0.0.2 188 | Compiling sgx_alloc v1.0.1 189 | Compiling sgx_tprotected_fs v1.0.1 190 | Compiling filetime v0.1.15 191 | Compiling sgx_build_helper v0.1.0 192 | Compiling sgx_tstd v1.0.1 193 | Compiling iovec v0.1.2 (file:///tmp/iovec-0.1.2) 194 | warning: unused import: `std::prelude::v1::*`=====================> ] 14/15: iovec 195 | --> src/unix.rs:21:5 196 | | 197 | 21 | use std::prelude::v1::*; 198 | | ^^^^^^^^^^^^^^^^^^^ 199 | | 200 | = note: #[warn(unused_imports)] on by default 201 | 202 | Finished dev [unoptimized + debuginfo] target(s) in 5.18s 203 | ``` 204 | 205 | 再删掉 `src/unix.rs` 下的 `prelude` 引用,这个应该就做完了。 206 | 207 | 然后递归操作其他剩下的 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)。 208 | 209 | ## 同时支持 cargo 和 xargo 210 | 211 | 这有一点点技巧性。首先需要明晰在 xargo 环境中是有一个所谓的 sysroot,包含了所有这个 target 下“环境自带”的 crate。在 Rust SGX 环境中的 sysroot 大概包括的 crates 可以查看[这里](https://github.com/baidu/rust-sgx-sdk/blob/master/release_notes.md#about-xargos-sysroot)。 212 | 213 | 以`sgx_trts`为例,在`cargo`环境下,需要在`Cargo.toml`里显式引用`sgx_trts`,但是在纯`xargo`环境下则不需要。所以,同时支持 `cargo + xargo`的`Cargo.toml`长成这个样子: 214 | 215 | ``` 216 | [target.'cfg(not(target_env = "sgx"))'.dependencies] 217 | sgx_tstd = "=1.0.1" 218 | sgx_trts = "=1.0.1" 219 | ``` 220 | 221 | 这里的语义不难理解。值得注意的是`target_env = "sgx"` 这里的值的来源是平台配置json:`x86_64-unknown-linux-sgx.json`。这里声明了:`"env": "sgx"`。所以在支持`xargo`时还需要这个json文件的。 222 | 223 | 对于之上的(3)步,也有一些改变。不再是向上述所说直接配置为`#![no_std]`,而是加入了一定的条件: 224 | 225 | ```Rust 226 | #![cfg_attr(not(target_env = "sgx"), no_std)] 227 | #![cfg_attr(target_env = "sgx", feature(rustc_private))] 228 | 229 | #[cfg(not(target_env = "sgx"))] 230 | #[macro_use] 231 | extern crate sgx_tstd as std; 232 | 233 | extern crate sgx_trts; 234 | ``` 235 | 236 | 这里的 `rustc_private` 是必须的,不然`xargo`不允许从sysroot里读取`sgx_trts`,而是强制用户从 crates.io 下载 `sgx_trts`。 237 | 238 | 排除最后一行`sgx_trts`不管,头部的5行是必须的,我把它叫做 “The Magic 5”。 239 | 240 | 对于一个具有复杂依赖关系的 Rust SGX enclave 来说,如果要用 `xargo` 编译,那么就一定需要为其指明 target json 和提供一个 `Xargo.toml` 来指导 `xargo`。如果要单独测试一个 crate 是否移植成功,则可以在其代码根目录(`Cargo.toml`所在的目录)下放置这两个文件,然后: 241 | 242 | ``` 243 | $ cargo b # 测试 cargo build 244 | $ RUST_TARGET_PATH=$(pwd) xargo build --target x86_64-unknown-linux-sgx # 测试 xargo build 245 | ``` 246 | 247 | 如果不指明`RUST_TARGET_PATH`,则`xargo`会在编译每个依赖项时去其代码根目录下寻找`x86_64-unknown-linux-sgx.json`,少一个都不行。这个“默认去每个依赖项下寻找json”的行为是在`xargo`的某个版本加上去的,为此我们不得不把json复制到了每个third_party的目录下…… 248 | 249 | ## 特殊的feature处理 250 | 251 | 这里以[`rust-crypto`](https://github.com/baidu/rust-sgx-sdk/tree/master/third_party/rust-crypto)为例。注意到原版[`rust-crypto`](https://github.com/DaGenix/rust-crypto/)对于`aesni`指令集的处理是根据这么写的: 252 | 253 | ``` 254 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 255 | pub mod aesni; 256 | ``` 257 | 258 | 所有支持 SGX 的 cpu 都是有 aesni 支持的(参考 [sgx-hardware](https://github.com/ayeks/SGX-hardware)),所以我们可以简单的强制开启`aesni`模块。我们 Rust SGX 的编译过程中没定义`target_arch`,所以就需要手工处理掉所有的 `target_arch` 相关判断,让这个库无条件的启用 `aesni` 模块。(其实加上`target_arch`可能更优雅一点?) 259 | 260 | # 写在最后 261 | 262 | 优秀的可移植性是 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,中间人彻底拜拜~(但是没法调试也是很蛋疼的……) 263 | 264 | -------------------------------------------------------------------------------- /05.md: -------------------------------------------------------------------------------- 1 | # 比 Fortanix 强在哪里 2 | 3 | 用一个最致命的问题来开头:原子性。 4 | 5 | 用最简单的 `RwLock` 来对比。我们的 `SgxRwLock`原子性最终来自于 Intel SGX SDK 使用 [`lock xchg`](https://github.com/intel/linux-sgx/blob/ba7f4defb679eab809fdb1f96062fcb2189619be/sdk/tlibc/gen/spinlock.c#L49) 指令提供的原子性: 6 | 7 | ``` 8 | static inline int _InterlockedExchange(int volatile * dst, int val) 9 | { 10 | int res; 11 | 12 | __asm __volatile( 13 | "lock xchg %2, %1;" 14 | "mov %2, %0" 15 | : "=m" (res) 16 | : "m" (*dst), 17 | "r" (val) 18 | : "memory" 19 | ); 20 | 21 | return (res); 22 | 23 | } 24 | ``` 25 | 26 | 由于这个操作是**在 Enclave 内**即可完成的,不需要借助 `ocall` 以及任何形式的输入,所以这个**原子性是可信的**。Intel SGX team 在设计 tstdc 的时候显然考虑了这一点。 27 | 28 | 再来看看 Fortanix 的 [RwLock 实现](https://github.com/rust-lang/rust/blob/f22dca0a1bef4141e75326caacc3cd59f3d5be8e/src/libstd/sys/sgx/rwlock.rs#L31): 29 | 30 | ``` 31 | #[inline] 32 | pub unsafe fn read(&self) { 33 | let mut rguard = self.readers.lock(); 34 | let wguard = self.writer.lock(); 35 | if *wguard.lock_var() || !wguard.queue_empty() { 36 | // Another thread has or is waiting for the write lock, wait 37 | drop(wguard); 38 | WaitQueue::wait(rguard); 39 | // Another thread has passed the lock to us 40 | } else { 41 | // No waiting writers, acquire the read lock 42 | *rguard.lock_var_mut() = 43 | NonZeroUsize::new(rguard.lock_var().map_or(0, |n| n.get()) + 1); 44 | } 45 | } 46 | ``` 47 | 48 | 看看 [`WaitQueue::wait`](https://github.com/rust-lang/rust/blob/f22dca0a1bef4141e75326caacc3cd59f3d5be8e/src/libstd/sys/sgx/waitqueue.rs#L143) 是怎么做的: 49 | 50 | ``` 51 | /// Adds the calling thread to the `WaitVariable`'s wait queue, then wait 52 | /// until a wakeup event. 53 | /// 54 | /// This function does not return until this thread has been awoken. 55 | pub fn wait(mut guard: SpinMutexGuard>) { 56 | unsafe { 57 | let mut entry = UnsafeListEntry::new(SpinMutex::new(WaitEntry { 58 | tcs: thread::current(), 59 | wake: false 60 | })); 61 | let entry = guard.queue.inner.push(&mut entry); 62 | drop(guard); 63 | while !entry.lock().wake { 64 | assert_eq!( 65 | usercalls::wait(EV_UNPARK, WAIT_INDEFINITE).unwrap() & EV_UNPARK, 66 | EV_UNPARK 67 | ); 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 聪明的读者不难发现:`WaitQueue::wait` 依赖于 `usercalls::wait`。`usercalls` 是 Fortanix 定义的 SGX ABI,其实就是把 Intel trts 里的 ocall 机制自己[重写了一遍](https://github.com/rust-lang/rust/blob/f22dca0a1bef4141e75326caacc3cd59f3d5be8e/src/libstd/sys/sgx/abi/entry.S#L221)。 74 | 75 | 这个 `usercalls::wait` 最后会在 untrusted 的代码中经由 ocall 函数来[实现](https://github.com/fortanix/rust-sgx/blob/2bffa4fa8af45619f090457fb72516fb29c92e5e/enclave-runner/src/usercalls/mod.rs#L780)。代码太长就不贴了。这些代码在 untrusted 区域维护了一个 event queue,最后再把事件通知给 enclave。于是攻击者如何攻击一个 Fortanix EDP 生成的 enclave 里的 RwLock 逻辑呢?直接攻击这个外界的 event queue 就 OK 啦。 76 | 77 | 在 Intel SGX 的威胁模型里,只有 CPU 自身是可信的,只有 Enclave 内的代码数据是可信的,并且 Intel 提供的软件栈非常仔细的考虑了这一点。然而 Fortanix 显然没有这样的水平,或者说他们并不是追求安全——而是追求方便。个人猜测这和 Fortanix 自从成立以来一直严重依赖于 Graphene SGX 有关。 78 | 79 | 接下来我不得不强调:Rust 自身的 libstd 有严重的缺陷。 80 | 81 | ## Rust libstd 的局限性 82 | 83 | Rust 自身的 libstd 有以下特性: 84 | 85 | * 在 libstd/sys 之外的部分是不可以用 feature 来控制功能的。意味着所有的 target 都必须提供 fs, net, env, time, thread, process 的实现,即使实现是 `unsupported!()`。 86 | * libstd 的外部依赖只包含 libc。Fortanix 的 abi 是第一个特例。 87 | 88 | 这些特性使得 libstd 极端不适合嵌入式系统以及可信计算。在许多平台上,根本没有线程、进程、时间的概念,根本谈不上 TLS、网络等等高层功能。在可信计算领域同理。 89 | 90 | 再回到上边讨论的 `RwLock`。可能有些读者已经注意到,在 Fortanix 的实现里,`wait` 在做 `usercalls` 时带了一个时间参数 `WAIT_INDEFINITE`。时间?为什么一个可信计算里会牵扯到时间?在 x86 平台上压根就没有可信时间(Intel ME 也不能保证时间可信),这里的时间是什么? 91 | 92 | 问题根源于 `RwLock` 自身带一个 timeout 属性 —— 这个属性通常是通过系统的 Futex 机制提供的,然而SGX 里并没有 Futex,更没有时间,这注定了 Intel SGX 环境下不可能提供一个完整语义的 `RwLock`。 93 | 94 | 所以在 rust-sgx-sdk 里提供的读写锁叫 `SgxRwLock`,只支持了大部分 `RwLock` 的功能,但是**保证了这个其是可信的。** 95 | 96 | ## 编译时出错 vs 运行时出错 97 | 98 | 显然,前者更好。在编写 Rust-SGX 程序时我们经常需要移植大量的库,此时如果使用 rust-sgx-sdk,那么可能会看到如下编译错误: 99 | 100 | ``` 101 | error[E0603]: module `fs` is private 102 | --> src/lib.rs:45:10 103 | | 104 | 45 | use std::fs; 105 | | ^^ 106 | ``` 107 | 108 | 看到这个信息就可以发现,有段代码引用了 `std::fs`。那么这个编译错误就会警告程序员:请改写这段代码,从可信文件系统 `std::sgxfs` 和不可信文件系统 `std::untrusted::fs` 中进行选择。这样的选择非常重要,它使得程序员可以考虑每一个输入和存储的可信性。毕竟可信计算只要引入了一丁点不可信输入,那都会引发灾难性的后果。 109 | 110 | 但是如果使用 Fortanix 的平台——这样的排查就完全无从下手了。有几个人会人工审计几十万行 Rust?如果有,请发简历给我! 111 | 112 | 113 | ## 功能性 114 | 115 | 这方面就不用多说了。rust-sgx-sdk 提供了 Intel SGX SDK 中各种各样的功能,例如密码学原语(比 Rust 社区的密码学原语不知道好到哪里去了),代码加密 PCL(Fortanix 要把 PCL 重写一遍吗?),可信文件系统(Fortanix 会有吗?),基于远程验证的 TLS 等等。这些丰富的功能足以击败所有类似的开发环境! 116 | 117 | ## The War 118 | 119 | 和 Fortanix 在 Rust 社区的战争很早就开始了。当 Fortanix 把 `target_env = "sgx"` 占据之后,我们的 `xargo` 编译就完全无法工作了。我们花了相当一部分时间来修正 Fortanix 的代码,在 libstd、compiler-builtins 以及一些亲儿子库里都有相应的 patch。在这些库中,Fortanix 提供的条件编译代码只判断了 `target_env` 而没有加 `target_vendor = "fortanix"` 使得所有 `sgx` 平台的代码都强制使用了 Fortanix 的 code base。幸好我们加入的 `target_vendor` 检查被顺利合入,否则世界上所有人都用不了 `sgx` 这个 `target_env` 了。 120 | 121 | 同样的战争发生在了许多第三方库中,比如著名的密码学库 ring。可以参考 issue [775](https://github.com/briansmith/ring/issues/775)。Fortanix 对于 `cpuid` 的态度是使用 AEX 做支持,显然他们对于 AEX 的代价没有什么认知。 122 | 123 | ## 结语 124 | 125 | 构建 SGX 程序需要非常仔细的考虑输入是否可信,这一点上 libstd 并不能提供相应的能力,进而导致 Fortanix 的方案非常的像一个 libOS —— 而这正是最需要**避免**的。还记得那句经典的:*If it compiles, it works.* 吗?在 SGX 环境下,这句话**远远不够**!我们的最终目标是: 126 | 127 | *If it compiles, it works, and it is trustworthy!* 128 | 129 | -------------------------------------------------------------------------------- /06.md: -------------------------------------------------------------------------------- 1 | # Intel(R) Software Guard Extensions Developer Guide 参考中译文 2 | 版权属于Intel, 如有出入,请以[原文档](https://download.01.org/intel-sgx/linux-2.2/docs/Intel_SGX_Developer_Guide.pdf)为准。 3 | ## 简介 4 | Intel SGX开发者文档提供了如何使用Intel SGX技术开发一个强壮的应用enclave。这份指南不是Intel SGX技术的简介和安全编码指南。这份指南假设在评估了使用Intel SGX开发的益处,代价和限制后,你决定选择使用这门技术,并且想要知道如何能正确地使用它去开发应用enclave。 5 | 6 | 这份文档借助一个假设通用的运行时系统(runtime System)中基础和原则,去构建相应的编程例子。这个运行时系统(run-time System)包括如下: 7 | * Untrusted Run-time System(uRTS,不可信运行时系统)-在intel sgx enclave环境外执行的代码,功能主要如下: 8 | * 加载和管理enclave 9 | * 对enclave发起调用,或从enclave中获取调用 10 | * Trusted Run-Time System(tRTS)-在intel sgx enclave环境内执行的代码,履行着如下的功能: 11 | * 管理着enclave自身的代码 12 | * 对enclave外部发起调用,并且接受相应的结果 13 | * 标准C/C++ 库和运行时环境 14 | * Edge Routines 15 | * 第三方库-它是一种特意移植进入到intel sgx enclave 环境内部工作的库。移植第三方库的主要原因在于可以简单地获取大量C代码,而无需专门地去重新开发library。 16 | 17 | 专业名词解释: 18 | * ECall:“Enclave Call”一个调用enclave内部接口函数的操作。 19 | * OCall:“Out call”从enclave内部向外部的应用发起调用的操作。 20 | * Trusted:任何在enclave内部trusted 环境下执行的代码或者数据结构。 21 | * Trusted Thread Context:线程的上下文存放在enclave中,主要包括以下几个部分组成: 22 | * Thread Control Structure(TCS)线程控制结构 23 | * Thread Data/Thread Local Storage 24 | * State Save Area(SSA)-线程异常处理机制 25 | * Stack-线程栈 26 | 27 | Untrusted:指代任何运行在应用的“untrusted”环境下(等价于enclave外部)的代码或者数据结构。 28 | 29 | ## Enclave 编程模型 30 | Intel SGX 软件,可以使用标准的工具和开发环境去进行开发。虽然编程范式与常规软件开发过程非常相似,然而在Intel SGX软件设计,开发,和调试的过程中仍然会有一些不同。 31 | 32 | 在这个章节中,我们比较了开发enclave和开发传统的应用,例如Android,Linux,OS X,windows 操作系统的异同点。相似的编程模式可以降低开发者进入Intel SGX 技术的门槛。然而,enclave的开发者必须意识到在Intel SGX软件的设计过程,开发过程和调试过程中,仍然有一些显著的差异,例如远程认证,秘密提供和密封。在这个文档的后面章节,我们将对它们进行阐述。 33 | 34 | 理解Intel SGX的编程模型后,开发者可以最大程度地去基于Intel SGX构建安全的应用。开发者必须仔细审视下面正确开发应用enclave的原则,不这么做的话可能会导致相应的安全问题,并在后面爆发出来。 35 | * enclave是一个庞大而单一的软件实体,目标在于将对于一个应用而言的可信计算基降低至可信运行时系统,ISV代码和第三方可信库。在上述组件中的任一个bug将会导致enclave的安全问题。 36 | * 非可信域控制了enclave 接口函数什么时候被调用的顺序。 37 | * 当调用进入enclave中,是由不可信域来在enclave中选择可信线程上下文去执行。 38 | * 并不保证传递进去enclave(ecall)中的参数和从enclave向外部发送的参数(ocall)是正确的,因为这是由不可信域提供的。 39 | * enclave向外部发送的调用OCall,不能保证不可信函数一定正确地被执行。 40 | * 任何人都可以加载一个enclave。更为严重的是,攻击者可能使用攻击性应用程序去加载一个enclave及,利用该enclave存在的安全问题,窃取相关数据。 41 | #### Enclave 文件格式 42 | 从高层上看,Intel SGX提供了类似于开发Android,Linux, OS X和Windows 应用的编程模型,某种意义上来说,enclave与Windows OS上的DLL,OS X上的Dynamic Library和Linux OS,Android OS上的Shared Object的开发有一定的相似性。 43 | 44 | 一个标准的DLL,Dynamic Library和Shared Object文件通常包括代码和数据部分。操作系统在当share library加载进去为首先分配一个堆,并且当线程创建时,为其分配一个相应的栈。 45 | 46 | 相似地,一个enclave library 文件包括可信代码和数据部分,但enclave被创建时,这些将会被导入到受保护的enclave 内存中。(即EPC)在一个enclave文件中,还存在着Intel SGX特定的数据结构,enclave元数据。元数据没有导入EPC中。取而代之的是,它被不可信的加载器加载,并决定enclave如何导入到EPC中。元数据中定义了可信线程上下文的数量,包括可信堆和可信栈的大小。在enclave初始化的时候,被可信运行时系统初始化。可信堆和可信栈是支持可信执行环境所必要的东西。元数据同时包括enclave的签名,这对于enclave的认证和初始化时非常重要的。 47 | 48 | 不管enclave中定义了多少个可信线程,在设计的过程中不能假设不可信应用将会以特定的顺序去调用Enclave中的接口函数。一旦enclave被初始化,一个攻击者可以调用Enclave接口函数,以任何可能的顺序去发起调用并且提供相应的输入参数。 49 | #### Encalve可信计算基 50 | ![image.png](https://upload-images.jianshu.io/upload_images/6907217-5610de40ddea8b97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 51 | 52 | 设计一个Intel SGX应用的第一步首先是那些资产是需要受保护的,以及这个资产包含着哪些数据结构,在这些数据结构上会执行哪些相关的操作,然后并将他们放入到一个单独的可信库中。由于ISV(独立软件开发商)最了解应用,ISV需要做应用的安全分析,并做最终的决定,将那些数据和代码放入到enclave中。 53 | 54 | enclave中的代码和存在于通常应用中的代码无太大的不同。但enclave初始化的时候,enclave的代码将会以一种特别的方式加载,特权代码和不可信应用的其它部分不能直接读取存在于保护环境中的代码,在enclave中也不能以毫无痕迹的方式改变代码的执行逻辑。因此,识别出一个安全软件开发过程中重要的秘密处理组件和涉及到这些关键步骤所使用的资源,并将它们放入到Intel SGX中是一个必不可少的过程。 55 | 56 | 将一个应用分为可信部分组件和不可信部分组件从安全的视角上有着其他的含义。通常意义上来说,更小的代码和数据通常更难以被检测出来。这意味更安全的软件通常有着更小的暴露出来的攻击面。因此,将应用的大部分移植进入到enclave,可能不是一种好的方式。当设计enclave时,可信计算基的大小是一个很重要的考虑因素。ISV应该尝试去最小化enclave的大小。 57 | 58 | 将应用进行拆分在Intel SGX应用管理电量事件的时候同时扮演着一种关键的角色。具体请查看Section Power ManangerMent章节。enclave中的状态信息越小,那么enclave能够备份相应的数据,并从相应的状态还原出来。 59 | 60 | #### Enclave Interface 函数(ECalls) 61 | 当在Intel SGX应用中定义了可信(enclave)组件和不可信(应用)组件后,开发者应该仔细地去定义非可信组件和可信组件之间的接口。ISV可信代码在以下的场景被执行: 62 | * 非可信应用明确地调用enclave内部的接口函数。从ISV接口函数发起ECall调用,和一个通用的应用调用shared library比较类似。 63 | * 当encalve向外部的应用发起的OCall返回时。OCall的返回和从一个shared library调用其他的shared library返回是比较类似的。例如,调用一个标准的C库去执行一个I/O操作。当一个OCall返回时,发起OCall的可信函数继续执行。 64 | * 当一个中断返回时,enclave代码同样被执行。然而,Intel SGX架构上的表现似乎中断从未发生过。这类似于执行shared library的过程中中断发生的状况。 65 | 66 | 一个enclave必须将ECcall和OCall的行为暴露出去,enclave开发者定义ECall和OCall的边界接口。ECall定义了不可信应用可能使用的接口,作为开发者应该尽可能地去限制ECall的数量。需要明确的是,enclave无法控制哪个ECall被执行,或者ECall将会以怎样的顺序被调用。因此,enclave不能期待enclave的调用时以某种特定的顺序的。另一方面,ISV接口函数仅仅只能在enclave初始化后被调用,这意味着: 67 | * 任何必要的地址基地址重置被正确地执行。 68 | * 可信全局数据,例如栈等被正确地初始化。 69 | * 可信线程的上下文,可信线程被正确地初始化。 70 | * 不直接说明的可信初始化函数执行完成。(例如ISV全局构造函数) 71 | #### Enclave的输入 72 | Enclave输入和输出可以被不可信的代码观察和修改。enclave开发者不应该信赖所有来自于不可信域的信息,并且在ECall的输入值和OCall的返回值时进行检测。为了防止针对输入函数的攻击,软件开发者应该采用加密的方式和完整性检测来保证enclave执行的可信性。 73 | 74 | 当一个encalve函数被调用时: 75 | * 函数的参数和其他任何通过引用传递的序列化数据参数进入到可信环境中,并且对于攻击者无法访问的。 76 | * 在参数上进行读写操作,返回值和其他序列化的引用,根据enclave开发者的参数与特殊定义,将不会影响ISV代码和数据的保密和完整性。 77 | * 参数和返回值,序列化数据被可信运行时分配和管理,不对任何ISV代码和数据进行覆盖。 78 | * 参数,返回值和序列化引用的大小被ISV所指定。 79 | 80 | ## 通过引用传递的输入参数 81 | 当ISV接口函数被调用时,输入参数将被停留在enclave中。然而,但一个输入参数通过引用的形式传递,只有引用(或指针)将会放置到enclave中。引用所指向的值停留在外部,并且会不断地变化。例如,一个攻击者可能在enclave检查函数参数后改变所引用的值。 82 | 83 | enclave开发者必须在使用引用或者指针时极其小心。一个应用可能将指向encclave内部的某一块内存地址的引用作为参数传递给enclave,这样可能会造成enclave无意地修改了代码和数据。相似地,如果enclave软件没有意识到一个指针指向一个不可信地址,enclave会泄漏秘密。为了阻止这些问题,enclave软件开发者必须决定是否使用指针。另外,enclave必须保证在它进行安全检查之后数据不能修改。开发者应该只传递enclave的范围中知道的接口对象,C的数据结构满足要求,而C++不符合。 84 | 85 | #### 外部Enclave调用(OCall) 86 | Enclave无法直接访问OS提供的服务。作为替代的是,encalve必须发起一个OCall将其传递到不可信应用中。虽然外部调用带来了损耗,但是并没有保密性的泄漏。 87 | 88 | 虽然OCall在某些时刻是必须的,但是enclave外部调用仍然存在着一些安全问题: 89 | * Enclave操作例如需要OCall支持的,比如线程同步和I/O,将会暴露到不可信域中。一个enclave必须以防止泄漏任何边车道信息为目的,否则这将会允许攻击者观察不可信函数调用,获取相应的enclave的秘密数据。 90 | * Enclave必须准备去处理OCall函数没有被完全执行的场景。OCall的返回值,作为enclave的输入,来自于不可信域中,并且是不可被依靠的。一个OCall可能看起来真正完成,其实并没有。例如,一个攻击者可能对丢掉enclave去写入密封数据到硬盘的enclave请求,并告知enclave已经成功写入。 91 | * 一个enclave不能依赖嵌套ECall(包含OCall)以之前固定的顺序执行。一个开发者可能在OCall的过程中限制ECall,虽然状态信息可以被存储在enclave中。然而,一个一个enclave发起OCall,没有人保证不可信域将会以递归调用的方式进入到enclave中。这样enclave将无法控制调用的顺序。 92 | 93 | 当一个enclave内的ISV函数发起OCall时: 94 | * OCall仅仅暴露OCall的参数和返回值到不可信域中。 95 | * 当一个OCall发生时,返回值和任何序列化数据的引用将传递进入可信执行环境中,对攻击者不可以访问。另外enclave需要检查指针。 96 | * 当一个OCall发生时,可信线程上下文与OCall之前的相同,除了在栈上的数据和易变寄存器上的值。 97 | 98 | #### 第三方库: 99 | 我们在之前提到了enclave的代码必须所有的安全检查在enclave内部的ISV接口函数中,同样的,暴露给非可信域的其他enclave接口函数。对于第三方可信库同样如此推荐。如果一个可信库依赖任何暴露的函数,ISV必须确认该函数同样进行了参数检查。 100 | 101 | 如果一个可信库的函数仅仅会在enclave内部调用,或者可信库是开源实现的enclave版本,参数检查可能不是很严格。但一个第三方库没有检查它的参数,不推荐直接修改第三方库的代码。enclave开发者可以在API外面添加一个wrapper。这并不会改变第三方的实现,同时也移除了风险。 102 | 103 | ## Enclave 签名 104 | 在软件中通过enclave建立信任的过程中主要有着以下三种行为: 105 | * measurement(度量):作为enclave在可信环境中初始化时用来验证文件准确性的身份。 106 | * Attestation(认证):向其它实体证明当前的环境被正确的初始化。 107 | * Sealing(数据的密封):通过某种方式保证可信环境上的数据可以正确地持久化和重新加载。 108 | 109 | 这一部分主要聚焦于measurement。认证和密封将在接下来的章节描述。 110 | Enclave包含一个来自于enclave持有人的自签名证书,同样被称为Enclave签名(SIGSTRUCT)。 111 | Enclave签名包含允许Intel SGX硬件检测enclave的完整性是否被篡改的信息。这可以保证Enclave被正确地加载到EPC中,并且是可信的。由于硬件仅仅在enclave被加载的过程中验证measurement,这意味着任何人可以修改enclave,并且使用他/她的key对enclave进行签名。为了阻止这种类型的攻击,enclave的签名同时识别enclave持有人的身份。encalve 签名包含这几个如下的重要字段,对于外部实体的认证非常重要。 112 | 113 | * Enclave Measurement- 一个简单的256位哈希值用来标注代码和放入到enclave中的初始化数据,包括他们放入到EPC中期待的顺序和位置,以及这些页面的安全属性。即使这个变量中的一个发生变化都会产生不同的值。当enclave代码/数据加载到EPC中,CPU开始计算enclave measurement并且将这个值存储在MRENCLAVE 寄存器中。接着CPU比较MRENCLAVE寄存器中的内容和存放在SIGSTRUCT中的enclave measurement值。当且仅当它们相等的时候,CPU允许enclave被初始化。 114 | * Enclave所有者的公钥-当一个enclave被正确地初始化,CPU将enclave所有者的公钥的hash存放在MRSIGNER寄存器中。MRSIGNER中的内容将会用作enclave所有者的身份证明。被同一个key认证的enclave在MRSIGNER寄存器中的值相同。 115 | * Enclave的安全版本号(ISVSVN)- enclave所有者对于enclave的每个版本号分配一个安全版本号。安全版本号反映了enclave的安全属性级别,并且需要单调递增,随着安全属性的不断上升。在一个enclave被正确初始化后,CPU记录SVN,并且在远程认证的过程中被使用。一个有着相同安全属性的enclave的不同版本应该分配相同的安全版本号。例如,一个没有解决安全相关bug的新版本enclave需要与旧版本的enclave持有相同的安全版本号。 116 | * Enclave的产品ID(ISVPRODID)- enclave所有者针对每一个enclave分配一个产品ID。产品ID允许enclave所有者使用相同的enclave所有者身份去分割enclave。当一个enclave被正确的初始化后,CPU记录Product ID,在远程认证的过程中可以被使用。 117 | 118 | enclave的开发者必须提供一个enclave的安全版本和产品ID,以及一对签名公私钥用来生成enclave签名。CPU获取enclave所有人的公钥的身份,私钥用来对enclave进行签名。enclave mesasurement的计算必须基于代码和被加载的enclave中的初始数据以及页面的安全属性。这些代码和被加载到enclave中的初始数据和页面的安全属性是由编译器生成的,他们的导入是由enclave加载器控制的。 119 | 120 | #### 保护Enclave签名的密钥 121 | Enclave签名公钥是enclave身份的一部分,对于保护它的秘密而言非常重要。一个拿到ISV的签名私钥可以发起以下的攻击行为: 122 | * 写下一个含有恶意的enclave,并且成功地认证为合法的enclave 123 | * 根据恶意的enclave写下中间件,获取个人平台上的隐私信息。 124 | 125 | 标准密钥管理实践提供了安全地保护私钥的方法,主要有以下几种: 126 | * 只获取私钥的最小访问权限。 127 | * 使用HSM(硬件加密机)或者其他enclave来保存私钥,并且执行私钥签名。 128 | * 将发布密钥和测试密钥分离,使用不同的密钥对。 129 | 130 | SDK提供了一个工具,用于enclave的签名,叫做sgx_sign。它获取enclave文件,并将enclave的签名添加进去。这个工具支持一步测试签名使用配置在本地系统的测试密钥,也可以使用两步签名,使用在签名设备或者平台中受保护的发布密钥进行签名。 131 | 132 | sgx_sign同时可以从一个签名的enclave file中生成白名单材料。 133 | 134 | #### 维护干净的开发环境 135 | ISV必须维护开发环境不受到恶意软件和其它潜在线程的干扰。 136 | 137 | ## Enclave认证 138 | 认证是用来证明一段软件成功地在某个平台上运行的过程。在Intel SGX中,认证是一种由第三方实体验证软件是否运行在开启Intel SGX的平台中,运行在enclave上从而实现了秘密和私有数据的保护的一种机制。认证依赖于平台去生成正确反映enclave签名的证书,同时包含这个enclave安全属性的信息。Intel SGX支持两种形式的验证机制。一种是同一平台的enclave之间的基本认证。另外一种是提供了enclave和远端第三方之间的认证。 139 | #### 本地认证 140 | 应用开发者可能希望编写的enclave能够和平台上的另外一个enclave在较高层次的接口进行合作调用。为了实现这个目标,开发者需要一种机制来使enclave可以向平台上的另外一个enclave证明他的身份和合法性。Intel SGX提供了可信硬件基机制来实现这个目标。一个enclave可以请求硬件生成一个证书,即report,其中通过密码学证据证明着enclave存在这个平台上。这个report可以给其他的enclave,让其进行验证report是同一平台上生成。内部enclave之间认证机制使用的是对称密钥,只有验证report结构的enclave和创建enclave report的硬件可以访问到,并且嵌入到硬件平台中。 141 | 142 | 一个enclave report包含着以下的信息: 143 | * enclave中的code和初始化数据的measurement。 144 | * enclave初始化的时候记录ISV证书公钥的哈希值。 145 | * 用户后来添加的数据。 146 | * 其他安全相关的状态信息。 147 | * 上述所有数据的一个签名,可以被生成report的同一平台去进行验证。 148 | 149 | ![image.png](https://upload-images.jianshu.io/upload_images/6907217-d9ca36e7658b7def.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 150 | 151 | 图上本地认证例子展示了同一平台上的两个enclave之间如何进行互相认证的过程。 152 | * 在上图中,应用A持有enclave A,应用B持有enclave B。在不可信应用A和B为两个enclave建立通信后,enclave B向enclave A发送了MRENCLAVE的值。 153 | 154 | 说明:应用A和B可以是同一应用。 155 | 156 | 有两种方法对于应用而言可以提取enclave的MRENCLAVE measurement。 157 | * 应用B从enclave B的enclave证书中提取MRENCLAVE的值。 158 | * enclave B提供了一个接口来导出该值,通过创建一个report。 159 | * Encalve A请求硬件生成一份report送至enclave B,并且其中包含着从enclave B获取到的数据。enclave A通过不可信应用将数据发送给enclave B。 160 | * 当enclave B收到了来自enclave A的报告,enclave B要求硬件去验证report来确认enclave A和B在同一平台上运行。enclave B从enclave A中提取相应的MRENCLAVE,然后请求硬件生成自己的report,接着发送给enclave A。 161 | * enclave A验证enclave B的report,并且验证enclave B和自己运行在同一平台上。 162 | 163 | #### 远程(跨平台认证)认证 164 | 一个持有enclave的应用可以要求enclave去生成一份report,接着将这份report传到平台服务去生成一种类型的证书,反映着enclave和平台的状态。这种类型的证书被称为quote。这个quote可以传递到平台外的实体,并且使用Intel Enhanced Privacy ID(Intel EPID)签名技术去进行验证。作为结果,CPU的密钥并没有直接从平台内部泄露出去。 165 | 166 | 一个quote包含这以下的数据: 167 | * enclave中代码和初始化数据的measurement。 168 | * enclave初始化的时候记录ISV证书公钥的哈希值。 169 | * enclave的产品ID和安全版本号。 170 | * enclave的属性,例如,encalve是否运行在debug mode。 171 | * enclave中添加的用户数据。提供了一种方式由enclave向外部实体提供数据。 172 | * 上述所有数据的一个签名,借助于Intel EPID组签名技术。 173 | 包含在quote中的enclave 数据(MRENCLAVE,MRSIGNER,ISVPRODID,ISVSVN, ATTRIBUTES,等等)在远程认证过程的末尾提供给远程服务使用上。服务提供者将根据这些数据去进行判断是否可信。 174 | 175 | #### Intel® Enhanced Privacy ID (Intel® EPID) 176 | 如果由enclave所在的机器使用自己的cpuid对quote进行签名,那么cpu的身份也会被泄密进而导致隐私问题。为了解决这个问题,Intel引入了匿名签名技术,即Intel Enhanced Privacy ID(Intel EPID),用来签名enclave quote。 177 | 178 | Intel EPID是一种组签名技术,允许平台去匿名签名对象并且可以保护签名者的隐私。通过Intel EPID签名技术,组里面的每一个签名者都有他们各自的私钥,但是验证者食用相同的公钥去验证个人的签名。因此,用户无法识别出两个交易是否来自于同一个机构,因为用户无法检测中是组里面哪个成员做了签名。在Intel SGX中,这个组是所有支持Intel SGX平台的集合。 179 | #### The Quting Enclave 180 | Intel中内嵌了一种特殊的enclave,成为Quoting Enclave(QE),QE验证report正确地按照起MRENCLAVE mesurement值进行创建,然后将它用一个硬件特定的非对称密钥(Intel EPID key)进行签名。输出的内容称为quote,可以在外部的平台中进行验签。当enclave 系统正在运行的过程中,只有QE有权限访问Intel EPID key。因此quote可以看作直接来自于硬件本身但是CPU key永远不会暴露到平台外部。 181 | #### 远程认证过程 182 | 下面的图展示了一个远程认证的过程: 183 | 184 | ![image.png](https://upload-images.jianshu.io/upload_images/6907217-fd58c9201520287c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 185 | 186 | 上图远程认证过程的例子展示了包括一个最简洁的enclave认证基本的过程。步骤描述如下: 187 | * 当应用需要在平台外部的服务,它首先和外部服务提供商建立连接。服务提供商需要应用证明它确实运行在enclave中。服务提供商并向其发送了一个有时间限制的nonce。 188 | * 应用从应用的enclave请求一份report,并将来自外部服务提供商的nonce传递进enclave中。 189 | * enclave生成一份report结构。 190 | * report发送给QE去做签名。 191 | * QE验证report。 192 | * QE转换report的body转换为quote,并对其使用EPID进行签名。 193 | * QE返回quote structure 请求。 194 | * 应用返回quote结构体,和其他相关的信息到外部服务商 195 | * 外部服务商使用EPID验证公钥去验证quote的签名信息。 196 | * 外部服务商对比来自enclave的信息,并与其它可信配置的信息进行比较。判断enclave是否满足条件。其中包括检测enclave是否运行在debug模式,及其的measurement,产品ID和enclave所有人等等。 197 | 这些步骤可以作为一个简单的例子,用来描述enclave如何被外部实体验证。 198 | 199 | 上述过程8中只是简单作为从enclave到外部服务提供商验证的例子。对于外部服务提供商想要获取可信配置信息的机制不再本节讨论中。 200 | #### 区分不同运行着的实例: 201 | Intel SGX不直接提供一种机制(例如,通过自动生成的REPORT字段)来区分同一个enclave的两个不同的实例。 202 | 203 | 如果想要尝试的话,可以使用RDRAND函数生成随机数,并作为作为用户数据,嵌入到REPORT中。 204 | 205 | ## 秘密提供 206 | 一个远程的实体可能在完成enclave的远程认证后向enclave发送一些秘密数据。通常情况下,秘密的发送通过安全通道来完成。安全通道的建立一定要在远程验证的过程中。否则,远程的服务器可能会提供给另一个实体秘密,而非认证的实体。这种攻击方式通常称为“中间人攻击”。 207 | 208 | 下面将描述如何实现秘密发送的过程。 209 | * 在远程认证过程中,发送的report中可以包含用来创建可信通道的公钥数据。为了实现这个目的,在步骤三中enclave可能想首先去认证服务器,来保证它是从一个可信的实体受到的秘密。例如,一个好的根证书可以嵌入到enclave的代码或初始化数据中,允许enclave去验证服务器。一旦远程服务器被验证,enclave会生成一个短暂的公私钥并将它的公钥塞入report的用户数据部分。 210 | * 当enclave完成远程认证的步骤七,服务器可以生成一个加密的公钥E,并将它用enclave的公钥P进行加密,将结果P(E)通过通道发送给应用。通道本身不需要安全保护,因为发送的秘密是加密的。 211 | * 一旦P(E)被enclave的应用接收,它可以传递到enclave中。在enclave中,它将解密P(E),因为它持有私钥,所以目前无论远程服务器还是enclave都持有加密密钥E。 212 | 213 | 如同远程认证一般,上面的例子并不是唯一的在enclave和远程实体之间建立传送数据的可信通道的方式,仅仅就是一个例子。 214 | 215 | 远程验证的验证着必须检测在提供任何秘密之前必须检测签名的身份(MRSIGNER)。Intel SGX技术不验证证书链当enclave被初始化的过程中。硬件也仅仅验证enclave的measurement(MRENCLAVE),并将在enclave 签名中的ISV公钥的哈希保存在MRSIGNER。这意味着任何人可以修改enclave并且重新签名。相似的是,验证者必须检查enclave的属性,避免提供秘密给任何处于debug模式下工作的enclave。 216 | 217 | 一旦安全通道建立起来,秘密将会发送给enclave。远程server可以使用密钥E对发送的秘密S进行加密,并将E(S)发送给应用,最终传送到enclave中。enclave可以使用密钥E对E(S)进行解密,然后获取S的内容。这会是不方便的,主要原因在于每次enclave初始化的时候都需要远程实体去连接enclave并完成秘密提供的这一个过程。取而代之的是,enclave可以借助下一章节中的密封技术等非易失性存储来存储秘密。即使密封的秘密从enclave中到处,秘密仍然对任何人是不可访问的。仅仅有enclave可以将它解封,并且仅仅局限于在它密封的那个平台(那台机器)上。 218 | 219 | 秘密提供是Intel SGX技术提供的一个非常关键的特性。它允许去构建比当前防篡改软件(TRS)更为强壮的应用。TRS简单地通过混淆来提供安全性,举例而言,它通过在可执行文件中混淆秘密来尝试从未认证机构手中保证秘密的安全性。然而,这种方法仅仅使时间变长,但仍然存在可能性,从TRS的二进制文件中提取秘密。此外,它对于开发者并不是非常友好。 220 | 221 | #### Debug Enclave 222 | 提供给一个debug enclave秘密数据是不安全。一个debug enclave的内存并不是受硬件保护的,可以通过Intel SGX debug指令进行查看和修改。在report和quote中,enclave的证书包含了enclave的属性,其中就有debug标志位。为了保证所有提供给产品级的enclave的秘密,本地和远程实体必须检查enclave的属性,在开发阶段可以交换某些调试秘密,但是生产环境下不应提供任何秘密给一个debug enclave。 223 | #### Enclave 秘密的清除 224 | Enclave秘密必须被安全地存储在enclave边界外部,当这样的秘密被密封后。然而,在某些场景下,需要在enclave中将这样的秘密清除。enclave开发者必须使用memset_s函数来清除任何包含私密数据的变量。这个函数将保证编译器不对写内存的方式进行优化(例如,借助cache然后之后写入到内存中),直接对秘密数据进行完整性的清除。当秘密数据保存在一个动态分配的缓存器中使用memset_s去清除数据是非常重要的。在这之后缓冲被释放,并且可以再分配。对于它之前的数据,如果他们没有被擦除,将可能泄露到enclave的外部。 225 | 226 | memset_s实现上没有做性能优化,而memset函数用来初始化buffer,并且清除不包含秘密数据的缓冲。 227 | 228 | 229 | ## Enclave 密封数据 230 | 当一个enclave初始化后,它对数据提供保密性和完整性保护,通过将它放置在enclave的边界中。enclave开发者当下面的事件(进而导致enclave被毁坏)发生时,可能有持久化秘密数据的请求,并将保存的数据还原出来。 231 | * 应用完成,并将enclave关闭。 232 | * 应用自身关闭。 233 | * 平台进入休眠或者关机状态。 234 | 235 | 通常情况下,提供给enclave的秘密在enclave关闭的时候丢失。但是遇到了上述的事件,如果在enclave中的秘密数据由于将来使用的需要需要被持久化,那么它必须在enclave关闭之前储存在enclave的边界之外。为了保护和持久化数据,提供了一种由enclave软件从特定的enclave中提取key的机制。这个Key只能在特定的平台上被某个enclave生成。enclave 软件使用那个key去在平台上加密数据或者从平台上解密已经存在的数据。我们把这种加密和解密的操作成为密封和解封。 236 | #### 软件密封策略 237 | 当密封数据的时候,enclave需要去指定数据被解封的时候需要满足什么特定的条件。目前有两种可供的选项: 238 | ##### 密封到当前的enclave(依照于enclave度量) 239 | 将数据密封到当前的enclave使用enclave measurement的当前版本(MRENCLAVE),但enclave创建的时候,将这个值绑定到密封操作所需要使用的key上。这个绑定操作由硬件执行通过EGETKEY指令。 240 | 241 | 只有有着相同的MRENCLAVE 度量的enclave才能够将密封的数据进行解密。如果enclave的DLL,Dynamic Library,或者Shared Object文件被篡改,那么enclave的measurement将会发生改变。作为影响,密封的key同样会发生变化,数据将不会被还原。 242 | ##### 密封到enclave当前的所有者 243 | 将数据密封到enclave的当前的所有者需要使用enclave所有者的身份,这个值由CPU存储在MRSIGNER寄存器中,在enclave初始化的过程中,并且把这个字绑定到密封数据函数所需要使用到的key中。这个绑定是由硬件通过EGETKEY指令完成。被密封数据的函数使用的这个key被绑定到enclave的产品ID上。产品ID存储在CPU中,但enclave初始化的时候。 244 | 245 | 当MRSIGNER 度量寄存器的值和产品ID相同时,enclave才能去解密以这种形式密封的数据。 246 | 在密封到enclave的基础上提供这种机制的好处是两方面。首先,它允许enclave的所有者对enclave进行升级,但是不需要进行复杂的升级过程来解密之前密封在之前版本的enclave(MRENCLAVE 度量值不同)中的数据,并且重新使用密封到新的版本。第二,它允许来自相同的所有者的enclave实现共享数据。 247 | 248 | Enclave所有者生产出enclave后,可以为之指定安全版本号。这个安全版本号同样在enclave初始化的时候存储在CPU中。一个enclave必须在从CPU中获取密封key的请求中必须提供一个相应的安全版本号。 249 | 一个enclave不能指定一个比它当前安全版本号靠后的enclave,问题在于它可能尝试获取当前还没有生成的encalve的seal key。然而,一个encalve可以指定一个在当前安全版本号之前的安全版本好。这个选项使enclave获得了解封之前版本的enclave中的数据,对于enclave软件更新,是有着巨大的好处的。 250 | 251 | #### 密封和解封过程 252 | 在enclave中密封数据的主要过程如下: 253 | * 为加密数据和密封数据结构在enclave中分配内存。其中密封数据结构主要包括要加密的数据和额外的认证数据组成。额外认证数据指额外的参与MAC计算但不进行加密的数据或文本。在加密数据结构中仍然是明文的数据。额外认证数据中的信息可能包括应用enclave,版本号,数据,等等。 254 | * 调用加密数据的API来执行加密操作,一个加密操作算法如下: 255 | * 验证输入参数是有效的。例如,如果作为参数进行传递的是一个指向加密数据结构的指针,它指向的缓存必须存在于enclave中。 256 | * 初始化并向其中填充一个将要被EGETKEY指令来执行的密钥请求数据结构。过程如下: 257 | * 获取EREPORT去获得安全ISV和TCB安全版本号,将在key获取中使用。 258 | * Key 名称:识别获取key的名字,在这种情况下即为Seal key。 259 | * Key Policy:识别即将使用的软件密封策略。使用MRSIGNER来表示密封到enclave的所有者上,使用MRENCLAVE来表示密封到当前的enclave(enclave measurement)中。保留位必须被清除。 260 | * 调用RDRAND来获取一个随机数。 261 | * 属性字段。表明密封密钥应该与什么属性进行绑定。 262 | * 使用上一步构造出的密钥请求结构调用EGETKEY获取Seal key。 263 | * 使用加密算法来使用密封密钥来执行密封操作。推荐使用AES-GCM加解密函数,例如Rijndael128GCM。 264 | * 将密封数据结构(包括密钥请求结构)从enclave中放到外部内存中,考虑到将来的使用。密钥请求结构将用来在enclave初始化后去获取密封密钥。 265 | 266 | 在enclave中对加密数据进行解密主要包括以下过程: 267 | * 为待解密的数据分配内存。 268 | * 调用解密api去执行解密操作。一个解密操作算法如下: 269 | * 验证输入参数的合法性。 270 | * 从密封数据结构中提取key request 数据结构,在密封数据结构中AAD数据是明文为加密的。 271 | * 借助key request数据结构去通过EGETKEY指令获取密封密钥。 272 | * 调用解密算法去使用密封密钥,执行解密算法。 273 | * 从内存中删除密封密钥以防止边侧道攻击引起的线路。 274 | * 验证解密算法生成的hash与加密过程中生成的hash一致。 275 | 276 | #### 区分不同的Enclave实例 277 | Enclave开发人员必须意识到即使两个同一个enclave的两个不同的实例可以在它们被认证的时刻被区分,但是目前Intel SGX不提供一种机制来阻止另外一个enclave实例去访问某个enclave生成的数据,但两个enclave同时使用EGETKEY指令时。所有的实例将会返回相同的密钥值,这是保证数据能够在电量时间触发后enclave关闭又重新加载数据成功的保证。 278 | 279 | 如果你需要类似的功能,推荐由enclave开发者为不同的enclave实例分配一个不同的身份,借助enclave签名机制。 280 | 281 | #### 跨平台的数据迁移 282 | 在Intel SGX技术之前,硬件平台从来不是可信计算基用来加密用户数据的一部分。这允许用户很容易地去移植他们的数据,即使已经加密过,从一个平台到另一个平台下。现在CPU帮助生成决定性的enclave 密封密钥。因此,跨平台的用户数据一直就成为了一个需要仔细关注的问题。 283 | 284 | 如果应用从一个旧的Intel SGX系统到一个新的Intel SGX系统(平台升级)或者从一个处理器到另一个处理器(系统中CPU替换或者云环境下的负载均衡),enclave将无法在新的平台上解密数据。数据迁移需要借助一个后端服务器来作为桥梁,实现共享数据的一直。无论ISV采取怎样的方法,seal key都不能泄漏出去,否则会导致安全性问题。 285 | 286 | #### Debug Enclave 287 | Intel SGX架构中包括了debug标志位,同时包含在key request structure中。在密封密钥的提取过程中,两种不同的encalve启动方式(debug和non-debug)将会获得不同的密封密钥。这个机制保护了生产环境中的密封数据,不会被一个debug的enclave解密。 288 | 289 | ## 程序性能 290 | Intel SGX 架构提供的安全保证对于应用的性能而言并不是无影响的。Intel会去最小化安全检查和安全机制带来的性能影响,但是还是存在着一些方法来最大程度地去实现应用的最好性能。 291 | 292 | 理解了本章描述的潜在性能损耗点,并且采用相关的编程推荐,将不会遇到显著的性能问题。 293 | #### Enclave 创建 294 | Enclave创建是第一个需要考虑的地方。enclave的大小很大程度上影响了创建enclave的时间,主要是由于在enclave的度量(measurement)过程中,需要进行一系列操作以保证所有的代码加载到enclave中是可信的。 295 | 296 | 在enclave的创建过程中,一系列的EADD和EEXTEND指令将被运行,主要用于加载和度量enclave的页面。 297 | * EADD每次加载4k字节的数据。 298 | * EEXTEND每次度量256字节的数据。这意味着对于EADD添加的4KB的数据,需要发起16次的EEXTEND调用。 299 | 300 | 从EADD和EEXTEND指令的工作上可以看出,创建enclave的时间和enclave的大小成正比。 301 | 302 | 相关的性能优化建议: 303 | * 减小enclave的大小。仔细检查在enclave中每段代码和数据元素,如有必要,将其移除。(使用工具:Intel VTune Amplifier)。例如,将即将静态链接的.o 文件先转换为诶.a文件。 304 | * Intel SGX 允许enclave通过Enclave Dynamic Memory Management(EDMM)的方式去进行扩展。当你的OS支持EDMM,那么可以先创建一个比较小的enclave,然后扩展它。 305 | * 通过在application中添加加载条的方式来获取用户的注意力,借此隐藏应用的加载时间。 306 | * 避免频繁的enclave创建和重新加载,进而最小化重复的加载性能损耗。 307 | #### 2.Enclave 切换 308 | 在enclave中控制权的频繁切换是第二个需要考虑的问题。下面解释了在不可信应用和enclave之间大量数据的切换和加载是如何影响着切换的时间,同时讨论了如何减小这方面的性能损耗。 309 | 310 | 在enclave之间的频繁转变需要在上下文之间切换非常多次。当一个EENTER指令触发进入enclave时,为了保证enclave可以正常运行,属于不可信运行时的注册状态(register state)和其它信息被存储,enclave中的线程状态和其它归属于可信状态的信息被加载。这部分工作主要是由SDK生成的代码执行。一个相反的过程发生在从enclave中退出(被EEXIT指令触发)。可信线程状态信息被保存,不可信的注册状态和其它信息被还原。在这个过程中同样执行着安全检查。这部分工作也主要由SDK生成的代码自动执行。这些行为构成了在应用和enclave之间控制权切换所带来的固定负载。 311 | 312 | 然而,这个转变中有一些可变变量影响着损耗。即传递参数的大小。参数在从不可信应用部分到可信enclave传递的过程中被序列化,并且返回的值被反序列化。从可信enclave中,来自不可信应用的参数被反序列化,并且将要返回的值被序列化。如果应用传递大量的参数,将会有显著的性能损耗。 313 | 314 | 如果在enclave和应用之间的控制权的转变很大程度地冲击着性能,考虑通过通过以下的方式降低影响: 315 | * 减小传递参数的总大小。(使用工具:Intel VTunte Amplifier) 316 | * 当确实有大量数据需要进行传递的时候,考虑使用指针的方式。**需要说明的是,使用指针会带来一部分安全风险,为此你必须自己去实现指针检查器,如果使用这种方式的话。** 317 | * 如果转变时间是一个问题,可以考虑一些其它的方式,例如由Google提出的Exit-less Service。[论文地址](https://sites.google.com/site/silbersteinmark/Home/cr-eurosys17sgx.pdf) 318 | 319 | 补充: 320 | 321 | 当执行enclave的过程中发生了中断,例如一条AEX指令被触发,将会导致enclave中暂停。一条AEX相对于标准的中断指令在enclave中有着更高的上下文切换负载。由于中断在操作系统的控制下,因此这里没有任何应用可以做工作的地方。但是理解中断如何影响性能是一件好事。 322 | 323 | 324 | ## 缓存未命中过多 325 | 第三个需要考虑的地方是缓存未命中的影响。在现在处理器中,缓存(cache)通常位于CPU中。引入缓存会对内存读写带来的影响众所周知,然而,Intel SGX在这个的基础上引入了其他的损耗,主要在于encalve所存储的内存内容在处理器cache之外的部分都是加密受保护的。这种类型的保护在从内存中获取cache lines带来了相应的负载。这种负载和Intel SGX实现方式有直接关系。 326 | 327 | Intel SGX架构中在缓存未命中的情况下,相对于其他传统的负载,新增了两种类型的负载: 328 | * 对于不在处理器cache中的每一条cache line所执行的完成check/anti-replay 检查,以及在系统内存中更新相应的数据结构(如果必要的话)。这种类型的负载依赖于内存访问模式。 329 | * 在cache和内存中加载和移除数据所带来的加解密。 330 | 当缓存未命中频率非常高时,这两类负载会对系统的性能产生很大的影响。(需要说明的是,访问在cache中的enclave内存不会受到影响。从enclave中访问在encalve内存外的数据有一些小的影响。) 331 | 如果你的系统受到大量的缓存未命中而相关的性能损耗的冲击,可以考虑下面的步骤: 332 | * 减小enclave中的数据的大小。观察数据,以保证只有必要的数据才能装载进入enclave中。更少的数据意味着更少的加减密和更少的数据结构检查,在Intel SGX内存控制/保护机制下。可以使用Intel VTune Amplifier来观察应用中的cache行为来制作相关的行动。 333 | * 可以查看下面的文档去创建一个更加“缓存友好”的应用:[PDF地址](https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf) 334 | #### 过多的页写入 335 | 第四个方面需要考虑的是Intel SGX应用频繁大量的页面切换对性能的影响,并且如何最小化这方面的后果。对于典型的操作系统而言,分页本身会带来性能损耗。然而,Intel SGX下分页所产生的性能损耗更严重,如下所述。 336 | 337 | Intel SGX使用安全存储,即EPC,来存储enclave中的内容。Enclave页面大小为4KB。当enclave比EPC总可用的内存要大的话,enclave分页机制可能会被某些特权软件来使用。当OS尝试交换enclave页面时,CPU使用EWB指令执行以下的步骤: 338 | * 读取要替换出去的Intel SGX页面(移出) 339 | * 加密页面中的内容 340 | * 写入加密的页面到未收到保护的系统内存中。 341 | 由于这个过程有着固定的负载,因此越多的页面被替换出去,越多的性能损耗发生。 342 | 343 | 为了阻止你的应用频繁经历这种页面的交换,尽可能确保enclave的大小小于EPC。尽可能只将秘密数据和在这之上的操作放入enclave中,从而最小化页面交换的可能性。可以使用Intel VTune Amplifier工具来观察应用中页面替换的行为,来保证做出正确的决定。 344 | 345 | #### 额外的性能说明 346 | 如果你的应用是多线程的,请从数据同步,锁,线程模型和内存分配算法上去寻找改善性能的方法。 347 | * Intel SGX sdk一些关于同步和锁的原语已经被优化。 348 | * 对于较重的多线程应用,推荐选择更好的内存分配算法。Intel SGX SDK针对linux提供了TCMalloc内存分配算法,相对于默认的dlmalloc内存分配算法,有着更好的性能表现。 349 | * 当有太多的enclave在运行时,尽管做了尽可能的性能优化,enclave的性能仍然可能不会达到预期。 350 | 351 | #### 电量管理 352 | 现代操作系统提供了允许应用被平台上电量事件通知的机制。当平台进入了电量管理的S3或者S4电量状态时(例如模拟休眠等),所有的enclave将会被清除,并且所有的key被清除。Enclave想要在进入S3,S4,S5的电量事件下保护相应秘密,那么必须将状态信息持久化到硬盘上。 353 | 354 | Intel SGX 架构上并不提供一种将电量事件直接传递进入enclave的方式。应用可以在OS层上对类似的事件注册一个callback函数。当回调函数被调用时,应用可以直接调用enclave,来保存相应secret的状态,将其持久化到硬盘上。然而,操作系统并不能保证为enclave提供足够的时间来密封它全部的内部状态。因此在可能遇到电量变化的事件时,需要持久化状态的enclave,必须阶段性地去密封enclave的数据(到enclave外部的硬盘或者云上)。当重新初始化应用的时候,enclave从零开始重新构建,并且基于它之前持久化的状态进行还原。 355 | 356 | 为了最小化地在硬盘上或者云上频繁地密封秘密和存储加密数据的性能损耗,enclave开发者应该在enclave中的持久化要存储的数据和状态信息的最小集,这样遇到电量变化的事件,enclave可以简单地完成持久化的任务。 357 | 358 | 359 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial-NoDerivatives 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 58 | International Public License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial-NoDerivatives 4.0 International Public 63 | License ("Public License"). To the extent this Public License may be 64 | interpreted as a contract, You are granted the Licensed Rights in 65 | consideration of Your acceptance of these terms and conditions, and the 66 | Licensor grants You such rights in consideration of benefits the 67 | Licensor receives from making the Licensed Material available under 68 | these terms and conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Copyright and Similar Rights means copyright and/or similar rights 84 | closely related to copyright including, without limitation, 85 | performance, broadcast, sound recording, and Sui Generis Database 86 | Rights, without regard to how the rights are labeled or 87 | categorized. For purposes of this Public License, the rights 88 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 89 | Rights. 90 | 91 | c. Effective Technological Measures means those measures that, in the 92 | absence of proper authority, may not be circumvented under laws 93 | fulfilling obligations under Article 11 of the WIPO Copyright 94 | Treaty adopted on December 20, 1996, and/or similar international 95 | agreements. 96 | 97 | d. Exceptions and Limitations means fair use, fair dealing, and/or 98 | any other exception or limitation to Copyright and Similar Rights 99 | that applies to Your use of the Licensed Material. 100 | 101 | e. Licensed Material means the artistic or literary work, database, 102 | or other material to which the Licensor applied this Public 103 | License. 104 | 105 | f. Licensed Rights means the rights granted to You subject to the 106 | terms and conditions of this Public License, which are limited to 107 | all Copyright and Similar Rights that apply to Your use of the 108 | Licensed Material and that the Licensor has authority to license. 109 | 110 | g. Licensor means the individual(s) or entity(ies) granting rights 111 | under this Public License. 112 | 113 | h. NonCommercial means not primarily intended for or directed towards 114 | commercial advantage or monetary compensation. For purposes of 115 | this Public License, the exchange of the Licensed Material for 116 | other material subject to Copyright and Similar Rights by digital 117 | file-sharing or similar means is NonCommercial provided there is 118 | no payment of monetary compensation in connection with the 119 | exchange. 120 | 121 | i. Share means to provide material to the public by any means or 122 | process that requires permission under the Licensed Rights, such 123 | as reproduction, public display, public performance, distribution, 124 | dissemination, communication, or importation, and to make material 125 | available to the public including in ways that members of the 126 | public may access the material from a place and at a time 127 | individually chosen by them. 128 | 129 | j. Sui Generis Database Rights means rights other than copyright 130 | resulting from Directive 96/9/EC of the European Parliament and of 131 | the Council of 11 March 1996 on the legal protection of databases, 132 | as amended and/or succeeded, as well as other essentially 133 | equivalent rights anywhere in the world. 134 | 135 | k. You means the individual or entity exercising the Licensed Rights 136 | under this Public License. Your has a corresponding meaning. 137 | 138 | 139 | Section 2 -- Scope. 140 | 141 | a. License grant. 142 | 143 | 1. Subject to the terms and conditions of this Public License, 144 | the Licensor hereby grants You a worldwide, royalty-free, 145 | non-sublicensable, non-exclusive, irrevocable license to 146 | exercise the Licensed Rights in the Licensed Material to: 147 | 148 | a. reproduce and Share the Licensed Material, in whole or 149 | in part, for NonCommercial purposes only; and 150 | 151 | b. produce and reproduce, but not Share, Adapted Material 152 | for NonCommercial purposes only. 153 | 154 | 2. Exceptions and Limitations. For the avoidance of doubt, where 155 | Exceptions and Limitations apply to Your use, this Public 156 | License does not apply, and You do not need to comply with 157 | its terms and conditions. 158 | 159 | 3. Term. The term of this Public License is specified in Section 160 | 6(a). 161 | 162 | 4. Media and formats; technical modifications allowed. The 163 | Licensor authorizes You to exercise the Licensed Rights in 164 | all media and formats whether now known or hereafter created, 165 | and to make technical modifications necessary to do so. The 166 | Licensor waives and/or agrees not to assert any right or 167 | authority to forbid You from making technical modifications 168 | necessary to exercise the Licensed Rights, including 169 | technical modifications necessary to circumvent Effective 170 | Technological Measures. For purposes of this Public License, 171 | simply making modifications authorized by this Section 2(a) 172 | (4) never produces Adapted Material. 173 | 174 | 5. Downstream recipients. 175 | 176 | a. Offer from the Licensor -- Licensed Material. Every 177 | recipient of the Licensed Material automatically 178 | receives an offer from the Licensor to exercise the 179 | Licensed Rights under the terms and conditions of this 180 | Public License. 181 | 182 | b. No downstream restrictions. You may not offer or impose 183 | any additional or different terms or conditions on, or 184 | apply any Effective Technological Measures to, the 185 | Licensed Material if doing so restricts exercise of the 186 | Licensed Rights by any recipient of the Licensed 187 | Material. 188 | 189 | 6. No endorsement. Nothing in this Public License constitutes or 190 | may be construed as permission to assert or imply that You 191 | are, or that Your use of the Licensed Material is, connected 192 | with, or sponsored, endorsed, or granted official status by, 193 | the Licensor or others designated to receive attribution as 194 | provided in Section 3(a)(1)(A)(i). 195 | 196 | b. Other rights. 197 | 198 | 1. Moral rights, such as the right of integrity, are not 199 | licensed under this Public License, nor are publicity, 200 | privacy, and/or other similar personality rights; however, to 201 | the extent possible, the Licensor waives and/or agrees not to 202 | assert any such rights held by the Licensor to the limited 203 | extent necessary to allow You to exercise the Licensed 204 | Rights, but not otherwise. 205 | 206 | 2. Patent and trademark rights are not licensed under this 207 | Public License. 208 | 209 | 3. To the extent possible, the Licensor waives any right to 210 | collect royalties from You for the exercise of the Licensed 211 | Rights, whether directly or through a collecting society 212 | under any voluntary or waivable statutory or compulsory 213 | licensing scheme. In all other cases the Licensor expressly 214 | reserves any right to collect such royalties, including when 215 | the Licensed Material is used other than for NonCommercial 216 | purposes. 217 | 218 | 219 | Section 3 -- License Conditions. 220 | 221 | Your exercise of the Licensed Rights is expressly made subject to the 222 | following conditions. 223 | 224 | a. Attribution. 225 | 226 | 1. If You Share the Licensed Material, You must: 227 | 228 | a. retain the following if it is supplied by the Licensor 229 | with the Licensed Material: 230 | 231 | i. identification of the creator(s) of the Licensed 232 | Material and any others designated to receive 233 | attribution, in any reasonable manner requested by 234 | the Licensor (including by pseudonym if 235 | designated); 236 | 237 | ii. a copyright notice; 238 | 239 | iii. a notice that refers to this Public License; 240 | 241 | iv. a notice that refers to the disclaimer of 242 | warranties; 243 | 244 | v. a URI or hyperlink to the Licensed Material to the 245 | extent reasonably practicable; 246 | 247 | b. indicate if You modified the Licensed Material and 248 | retain an indication of any previous modifications; and 249 | 250 | c. indicate the Licensed Material is licensed under this 251 | Public License, and include the text of, or the URI or 252 | hyperlink to, this Public License. 253 | 254 | For the avoidance of doubt, You do not have permission under 255 | this Public License to Share Adapted Material. 256 | 257 | 2. You may satisfy the conditions in Section 3(a)(1) in any 258 | reasonable manner based on the medium, means, and context in 259 | which You Share the Licensed Material. For example, it may be 260 | reasonable to satisfy the conditions by providing a URI or 261 | hyperlink to a resource that includes the required 262 | information. 263 | 264 | 3. If requested by the Licensor, You must remove any of the 265 | information required by Section 3(a)(1)(A) to the extent 266 | reasonably practicable. 267 | 268 | 269 | Section 4 -- Sui Generis Database Rights. 270 | 271 | Where the Licensed Rights include Sui Generis Database Rights that 272 | apply to Your use of the Licensed Material: 273 | 274 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 275 | to extract, reuse, reproduce, and Share all or a substantial 276 | portion of the contents of the database for NonCommercial purposes 277 | only and provided You do not Share Adapted Material; 278 | 279 | b. if You include all or a substantial portion of the database 280 | contents in a database in which You have Sui Generis Database 281 | Rights, then the database in which You have Sui Generis Database 282 | Rights (but not its individual contents) is Adapted Material; and 283 | 284 | c. You must comply with the conditions in Section 3(a) if You Share 285 | all or a substantial portion of the contents of the database. 286 | 287 | For the avoidance of doubt, this Section 4 supplements and does not 288 | replace Your obligations under this Public License where the Licensed 289 | Rights include other Copyright and Similar Rights. 290 | 291 | 292 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 293 | 294 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 295 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 296 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 297 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 298 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 299 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 300 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 301 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 302 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 303 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 304 | 305 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 306 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 307 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 308 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 309 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 310 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 311 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 312 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 313 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 314 | 315 | c. The disclaimer of warranties and limitation of liability provided 316 | above shall be interpreted in a manner that, to the extent 317 | possible, most closely approximates an absolute disclaimer and 318 | waiver of all liability. 319 | 320 | 321 | Section 6 -- Term and Termination. 322 | 323 | a. This Public License applies for the term of the Copyright and 324 | Similar Rights licensed here. However, if You fail to comply with 325 | this Public License, then Your rights under this Public License 326 | terminate automatically. 327 | 328 | b. Where Your right to use the Licensed Material has terminated under 329 | Section 6(a), it reinstates: 330 | 331 | 1. automatically as of the date the violation is cured, provided 332 | it is cured within 30 days of Your discovery of the 333 | violation; or 334 | 335 | 2. upon express reinstatement by the Licensor. 336 | 337 | For the avoidance of doubt, this Section 6(b) does not affect any 338 | right the Licensor may have to seek remedies for Your violations 339 | of this Public License. 340 | 341 | c. For the avoidance of doubt, the Licensor may also offer the 342 | Licensed Material under separate terms or conditions or stop 343 | distributing the Licensed Material at any time; however, doing so 344 | will not terminate this Public License. 345 | 346 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 347 | License. 348 | 349 | 350 | Section 7 -- Other Terms and Conditions. 351 | 352 | a. The Licensor shall not be bound by any additional or different 353 | terms or conditions communicated by You unless expressly agreed. 354 | 355 | b. Any arrangements, understandings, or agreements regarding the 356 | Licensed Material not stated herein are separate from and 357 | independent of the terms and conditions of this Public License. 358 | 359 | 360 | Section 8 -- Interpretation. 361 | 362 | a. For the avoidance of doubt, this Public License does not, and 363 | shall not be interpreted to, reduce, limit, restrict, or impose 364 | conditions on any use of the Licensed Material that could lawfully 365 | be made without permission under this Public License. 366 | 367 | b. To the extent possible, if any provision of this Public License is 368 | deemed unenforceable, it shall be automatically reformed to the 369 | minimum extent necessary to make it enforceable. If the provision 370 | cannot be reformed, it shall be severed from this Public License 371 | without affecting the enforceability of the remaining terms and 372 | conditions. 373 | 374 | c. No term or condition of this Public License will be waived and no 375 | failure to comply consented to unless expressly agreed to by the 376 | Licensor. 377 | 378 | d. Nothing in this Public License constitutes or may be interpreted 379 | as a limitation upon, or waiver of, any privileges and immunities 380 | that apply to the Licensor or You, including from the legal 381 | processes of any jurisdiction or authority. 382 | 383 | ======================================================================= 384 | 385 | Creative Commons is not a party to its public 386 | licenses. Notwithstanding, Creative Commons may elect to apply one of 387 | its public licenses to material it publishes and in those instances 388 | will be considered the “Licensor.” The text of the Creative Commons 389 | public licenses is dedicated to the public domain under the CC0 Public 390 | Domain Dedication. Except for the limited purpose of indicating that 391 | material is shared under a Creative Commons public license or as 392 | otherwise permitted by the Creative Commons policies published at 393 | creativecommons.org/policies, Creative Commons does not authorize the 394 | use of the trademark "Creative Commons" or any other trademark or logo 395 | of Creative Commons without its prior written consent including, 396 | without limitation, in connection with any unauthorized modifications 397 | to any of its public licenses or any other arrangements, 398 | understandings, or agreements concerning use of licensed material. For 399 | the avoidance of doubt, this paragraph does not form part of the 400 | public licenses. 401 | 402 | Creative Commons may be contacted at creativecommons.org. 403 | 404 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SGX从入门到放弃 2 | 3 | **本项目仅代表个人立场,与任何其他个人/组织无关** 4 | 5 | 搞SGX也有快两年了,我觉得取这个名字还是挺恰当——从入门到放弃,虽然到现在我还在坚持。 6 | 7 | SGX难学的一个主要理由是知识体系太碎了,没有什么统一的文档把东西都串起来,甚至有些知识点Intel自己都在含糊其辞。从我个人的经验看来,不吃透 [SGX SDK](https://github.com/intel/linux-sgx) 代码是很难写出一个“正确”的SGX程序的,我个人也没看到什么写的“正确”的SGX应用。 8 | 9 | 目前打算开个blog坑,每周写大概一个小时。尽量把我在捣鼓SGX里的经验和走的弯路都写一写。如果弃坑了也不要奇怪,抬头看看标题。┓( ´∀` )┏ 10 | 11 | 本文的第一个读者(和第一个coauthor)应该是 [@zhengmin1989](https://github.com/zhengmin1989) 如果他能看得懂的话应该还可以的吧…… 12 | 13 | License是取的比较严的 [CC-BY-NC-ND](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh) 。毕竟是很随意的东西,大家看看就好。 14 | 15 | 欢迎提issue,PR。个人对SGX的理解可能是有错误的,欢迎指出。 16 | 17 | 最后打个广告,这一年多做的 [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) 等等。 18 | 19 | 更新:[Chainlink](https://github.com/smartcontractkit/chainlink) 已经应用了 rust-sgx-sdk 并使用了我们移植的 WebAssembly 解释器! [Cargo.toml](https://github.com/smartcontractkit/chainlink/blob/393cbc896fe63c4ef44ab71218f0e89366d9b4e5/sgx/enclave/Cargo.toml) 20 | 21 | 22 | ## Index 23 | 24 | [00 SGX能做什么](00.md) 25 | 26 | [01 运行第一个SGX程序](01.md) 27 | 28 | [02 CVE-2017-5753 upgrade 参考译文](02.md) 29 | 30 | [03 Edger8r upgrade 参考译文](03.md) 31 | 32 | [04 移植](04.md) 33 | 34 | [05 比 Fortanix 强在哪里](05.md) 35 | 36 | [06 Intel(R) Software Guard Extensions Developer Guide 部分参考译文](06.md) 37 | 38 | # License 39 | Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. 40 | 41 | --------------------------------------------------------------------------------