Run unmodified c++ program under the support of graphene-sgx

Run unmodified c++ program under the support of graphene-sgx

SGX 是 Intel 近几年来才有的硬件安全特性,可以将应用放在一段加密内存中保护起来。在 SGX 的保护下即使 VMM、Kernel 完全被攻击者挟持,也无法攻击其中的应用。

但是 SGX 有一套自己的编程规范,需要定义 edl 文件,并且需要比较细粒度的对应用进行切分,划分 Trust/Untrust,这对编程人员来说造成了一定的困难。而 Graphene-SGX 则是 Graphene 的作者和 Intel 的专家合作开发的 libOS,可以在不修改应用的情况下将其运行在 SGX 中。

本文主要是记录我在学习使用 Graphene-SGX 时,第一次成功运行 c++ 的 hello-world 程序的过程。

安装 Graphene

Graphene 是 oscarlab 在 libOS 上的工作研究。在 EuroSys’14 上,该实验室发表了 Graphene 的相关研究成果。在 Intel SGX 出现后,在 ATC’17 上该实验室又和 Intel 合作发表了 Graphene-SGX 的相关研究成果。目前 Graphene 已经在 github 上开源,repo 地址:https://github.com/oscarlab/graphene

Graphene 的官方文档中对于如何使用介绍的内容比较少(也可能是我没读懂),我主要是通过学习其中的 example 摸索出如何使用这个 libOS。

根据文档的提示,首先下载 graphene ,可以通过 git clone https://github.com/oscarlab/graphene.git 的方式。

因为需要 graphene-sgx 的特性,因此 host 机器上必须安装 Intel-sgx-driver,以记 Intel-sgxsdk

文档在这一块的说明比较详细,有以下几点需要注意:

  1. 按照文档的说明,graphene-sgx 需要 Linux kernel 开启 FSGSBASE 特性,因此可能需要编译更新 kernel, 打 patch。

  2. clone 下来的 graphene 有一个 submodule 引用了 graphene-sgx-driver , 因此需要运行 git submodule update --init -- Pal/src/host/Linux-SGX/sgx-driver/

  3. 在文档 Quick Start 中的第 3 步中,注释提示,terminal 可能会要求我们输入 path to the Intel SGX driver code,这里其实就是输入 sgx.h 头文件的目录所在,可以在 Intel sgx driver 的安装目录找到 sgx.h,然后把目录复制过来就好。

    1
    2
    3
    4
    cd $GRAPHENE_DIR
    make SGX=1
    # the console will prompt you for the path to the Intel SGX driver code
    # (simply press ENTER if you use the in-kernel Intel SGX driver)

所有都编译成功后,运行 Quick Start 中的 test 也通过了,就说明安装成功了。

使用 Graphene-SGX

接下来就可以自己学习写一个 manifest,来运行我们的 c++ program。

第一次使用,我运行的程序比较简单,是 c++ 版的 hello-world, 代码如下:

1
2
3
4
5
#include <iostream>

int main() {
std::cout << "Hello, test!" << std::endl;
}

代码写好之后,我们就需要写一个 Makefile 文件,这里主要是需要参考 Examples/ 目录下的示例。

Makefile 编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Use one of these commands to build the manifest for Bash:
#
# - make
# - make DEBUG=1
# - make SGX=1
# - make SGX=1 DEBUG=1
#
# Use `make clean` to remove Graphene-generated files.

# Relative path to Graphene root and key for enclave signing
GRAPHENEDIR ?= ../..
SGX_SIGNER_KEY ?= $(GRAPHENEDIR)/Pal/src/host/Linux-SGX/signer/enclave-key.pem

ifeq ($(DEBUG),1)
GRAPHENEDEBUG = inline
else
GRAPHENEDEBUG = none
endif

.PHONY: all
all: test.manifest | test pal_loader
ifeq ($(SGX),1)
all: test.token
endif

include ../../Scripts/Makefile.configs

# Generating manifest rules for Bash dependenciess
test.manifest: manifest.template
sed -e 's|$$(GRAPHENEDIR)|'"$(GRAPHENEDIR)"'|g' \
-e 's|$$(GRAPHENEDEBUG)|'"$(GRAPHENEDEBUG)"'|g' \
-e 's|$$(ARCH_LIBDIR)|'"$(ARCH_LIBDIR)"'|g' \
$< > $@


# Generating the SGX-specific manifest (*.manifest.sgx), the enclave signature,
# and the token for enclave initialization.
test.manifest.sgx: test.manifest
$(GRAPHENEDIR)/Pal/src/host/Linux-SGX/signer/pal-sgx-sign \
-exec test \
-libpal $(GRAPHENEDIR)/Runtime/libpal-Linux-SGX.so \
-key $(SGX_SIGNER_KEY) \
-manifest test.manifest -output $@

test.sig: test.manifest.sgx

test.token: test.sig
$(GRAPHENEDIR)/Pal/src/host/Linux-SGX/signer/pal-sgx-get-token \
-output test.token -sig test.sig

test:
g++ test.cpp -o $@

pal_loader:
ln -s $(GRAPHENEDIR)/Runtime/pal_loader $@

.PHONY: all

.PHONY: clean
clean:
$(RM) *.manifest *.manifest.sgx *.token *.sig test pal_loader

.PHONY: distclean
distclean: clean

第 11 行 - 第 26 行基本上是 Examples 中的公共部分,这里主要是定义了一些变量,后面将会使用。

在 SGX enabled 时,需要生成的目标文件一共有:*.manifest, *.manifest.sgx, *.sig, *.token, *, pal_loader

*.manifest: 该文件主要是定义了 graphene 创建隔离环境时需要挂载的文件、挂载的地方、环境变量等等。具体的将会在后一节内容详细介绍。

*.manifest.sgx: 该文件是在 *.manifest 的基础上对依赖的 trusted_files 进行了签名,以用于加载到 enclave 时进行完整性检查。

*.sig:

*.token:

*.pal_loader:

Manifest 编写

Manifest 中主要定义一些 Graphene 需要挂载的库、挂载的路径,环境变量,SGX 相关配置,SGX 信任的文件等等。

General 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
loader.argv0_override = "$(ARGV0_OVERRIDE)"

# Read application arguments directly from the command line. Don't use this on production!
loader.insecure__use_cmdline_argv = 1

# Graphene environment, including the path of the library OS and the debug
# option (inline/none).
loader.preload = "file:$(GRAPHENEDIR)/Runtime/libsysdb.so"
loader.debug_type = "$(GRAPHENEDEBUG)"

# Environment variables
loader.env.LD_LIBRARY_PATH = "/lib:$(ARCH_LIBDIR)::/usr/lib/x86_64-linux-gnu"
loader.env.PATH = "/"

第 1 行的 loader.argv0_override 是指在运行 ./pal_loader xxx 时,用 xxx 来代替 pal_loader,也就是运行这个 xxx 程序的意思。

第 11 行的 loader.env.LD_LIBRARY_PATH 指定了程序运行时需要的库加载路径,我猜想,程序运行时应该需要到这些路径上去搜索库。

Mount 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Mounted FSes. The following "chroot" FSes mount a part of the host FS into the
# guest. Other parts of the host FS will not be available in the guest.

# Default glibc files, mounted from the Runtime directory in GRAPHENEDIR.
fs.mount.lib1.type = "chroot"
fs.mount.lib1.path = "/lib"
fs.mount.lib1.uri = "file:$(GRAPHENEDIR)/Runtime"

# stdc++ files, mounted from /usr/lib/ARCH
fs.mount.lib2.type = "chroot"
fs.mount.lib2.path = "/usr/lib/x86_64-linux-gnu"
fs.mount.lib2.uri = "file:/usr/lib/x86_64-linux-gnu"

# Mount host-OS directory contanining libcrypt and NSS libraries.
fs.mount.lib3.type = "chroot"
fs.mount.lib3.path = "$(ARCH_LIBDIR)"
fs.mount.lib3.uri = "file:$(ARCH_LIBDIR)"

# Mount /bin
fs.mount.bin.type = "chroot"
fs.mount.bin.path = "/bin"
fs.mount.bin.uri = "file:/bin"

接下来就是挂载,可以看到,在这次使用中,我挂载了四个目录,之所以这样,是因为 hello-world 这个程序的依赖包含了不同路径的库。

要检查某个程序依赖哪些库,可以通过 ldd xxx 来查看,ldd xxx 的结果应该都需要挂载到 graphene 中。

manifest 中所有的 bash-like variable 都需要在 Makefile 中将其替换成字面量。这个也是 *.manifest 目标文件生成时所需要做的主要工作。可以看到,在这个 Makefile 中,主要是通过 sed -e 's|xx|'xx'|g' 来完成的。

SGX 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Set the virtual memory size of the SGX enclave. For SGX v1, the enclave
# size must be specified during signing. If the program needs more virtual
# memory than the enclave size, Graphene will not be able to allocate it.
sgx.enclave_size = "256M"

# Set the maximum number of enclave threads. For SGX v1, the number of enclave
# TCSes must be specified during signing, so the application cannot use more
# threads than the number of TCSes. Note that Graphene also creates an internal
# thread for handling inter-process communication (IPC), and potentially another
# thread for asynchronous events. Therefore, the actual number of threads that
# the application can create is (sgx.thread_num - 2).
sgx.thread_num = 4

# SGX trusted libraries
# Glibc libraries
sgx.trusted_files.ld = "file:$(GRAPHENEDIR)/Runtime/ld-linux-x86-64.so.2"
sgx.trusted_files.libc = "file:$(GRAPHENEDIR)/Runtime/libc.so.6"
sgx.trusted_files.libm = "file:$(GRAPHENEDIR)/Runtime/libm.so.6"
sgx.trusted_files.libdl = "file:$(GRAPHENEDIR)/Runtime/libdl.so.2"
sgx.trusted_files.librt = "file:$(GRAPHENEDIR)/Runtime/librt.so.1"
sgx.trusted_files.libutil = "file:$(GRAPHENEDIR)/Runtime/libutil.so.1"
sgx.trusted_files.libpthread = "file:$(GRAPHENEDIR)/Runtime/libpthread.so.0"

# stdc++ libraries
sgx.trusted_files.libstdcpp = "file:/usr/lib/x86_64-linux-gnu/libstdc++.so.6"
sgx.trusted_files.libgcc_s = "file:/lib/x86_64-linux-gnu/libgcc_s.so.1"

最后,sgx 部分主要需要配置 enclave 的相关参数,这和 SGX 编程模型中的 xml 配置文件的配置基本一致。

sgx.trusted_files.[identifier] 是在程序运行时需要加载的库,可以通过 ldd 来查看,应该是每一个依赖都需要列举在此处。但是由于我是第一次使用,所以也只能凭感觉,如果有错误,希望大家能指出!

编译运行

编写好之后,就可以通过简单的命令直接运行我们的 hello-world 程序。

1
2
SGX=1 make
SGX=1 ./pal_loader test

如果一些顺利,将会输出 Hello, test!

image-20201214181747765

哈哈哈,这是很简单的一个program,但是成功输出的时候,真的很开心!

参考

  1. Graphene 官方文档 https://graphene.readthedocs.io/en/latest/quickstart.html
  2. GNU Make 文档 https://www.gnu.org/software/make/manual/make.html
  3. Intel SGX 文档 https://download.01.org/intel-sgx/linux-1.7/docs/Intel_SGX_SDK_Developer_Reference_Linux_1.7_Open_Source.pdf

评论