SGX

1 SGX

SGX: Software Guard Extensions

在运行时,程序可以分为几个部分:

  1. Untrusted Run-Time System:在SGX enclave外部执行的部分,负责加载和管理一个enclave,并且Ecall enclave,接受enclave中的Ocall。
  2. Trusted Run-Time System:在SGX enclave内部执行的部分,接受Ecall,进行Ocall,并对enclave自身进行管理。
  3. Edge routines:指的是函数边的情况。
  4. 第三方库:为SGX定制的库。

  • ECall:Enclave call,调用enclave当中的函数。
  • OCall:Out call,从enclave内部到外部的调用。

在SGX中,enclave是用来减少信任基的,在运行时不可信域决定了可信域函数的调用顺序,也决定了起内部的上下文;并且ECall和OCall的返回值和参数也是不可信的。

sgx


文件格式:除了代码段、数据段之外,enclave文件还包含metadata,一个untrusted loader需要使用这个metadata来决定这个enclave如何被装载。

2 Enclave接口

将一个应用划分为trusted和untrusted两部分之后,需要定义二者之间的接口。不可信的应用通过ISV接口函数,对共享库进行调用(ECall);而从Enclave对外部进行调用时(OCall),在函数执行完后会返回可信的区域继续执行;中断也不会破坏这个过程。

Enclave需要暴露一部分接口给外部应用(ECalls),同时又要声明哪些外部提供的服务(OCalls)是必须的。

Enclave的输入和输出都是不可信的代码可见的,因此enclave不能信任任何不可信域中的信息,检测ECall的输入参数和OCall的返回值。
Enclave中的参数等,都保存在可信的环境中,并且其读写不会对ISV代码和数据的完整性造成影响;而参数的长度、返回值等都由ISV来指定。对于引用的输入,enclave会进行更特殊的处理,确定指针所指的内存区域是否在enclave的线性范围之内。

对于操作系统的服务,Enclave是不能直接使用的,必须通过OCall作为接口,其return值作为输入传回给Encalve,这个值也是不可信的。

如果在OCall中使用了ECall,这就是一个nested ECall,使用者应该避免这种情况的发生,如果必须使用,则要对接口进行限制。

Enclave Definition Language

EDL文件,用来描述enclave当中的trusted和untrusted部分。在Linux中,Edger8r Tool通过这个文件来创建enclave的C wrapper函数,也即ECALL和OCALL所使用的函数。

//EDL Template
enclave	{
	//包含文件和作为参数的数据结构
	trusted {
		//任何enclave_t.h中包含的文件
		//trusted function原型
	};

	untrusted {
		//任何enclave_u.t中包含的文件
		//untrusted function原型
	};
};

EDL文件不允许include有自定义类型的头文件。对于全局的包含文件,也不会包含在enclave当中。这类情况会使用不同的头文件,例如SGX会利用SDK所提供的stdio.h,而应用会使用由编译器提供的stdio.h。

EDL中的数据

EDL中可以使用基本的关键字,包括

char, short, long, int, float, double, void, int8_t, int16_t, int32_t, 	int64_t, size_t, wchar_t, uint8_t, uint16_t, uint32_t, uint64_t, unsigned, 	struct, enum, union

其它类型可以在头文件中包含。用户定义的数据类型可以在EDL中使用,但要遵守其编写的规范。正确的定义如下


	enclave{
		include "user_types.h" 

struct struct_foo_t { uint32_t struct_foo_0; uint64_t struct_foo_1; }; enum enum_foo_t { ENUM_FOO_0 = 0, ENUM_FOO_1 = 1 }; }; trusted { public void test_char(char val); public void test_int(int val); public void test_long(long long val); };

EDL中的指针

EDL中定义有一些和指针一起使用的值,这些值是用在ECALL和OCALL时使用的参数上的。指针需要用in,out,或者user_check进行明确的修饰。其中[in]和[out]说明的是方向:

  • [in]表示参数从调用方传递到被调用放,对ECALL来说,in是从应用程序传递到enclave中,对OCALL来说则表示参数从应用程序传递到enclave中。
  • [out]表示参数是从被调用方返回到调用方。对ECALL来说,out表示参数从enclave传递到应用中,对OCALL来说则是从应用传递给enclave。
  • [in]和[out]组合使用表示参数是双向传递的。

方向属性能够用来提供保护,但会降低性能。如果使用user_check,则表示在不可信内存中的数据会在使用前进行验证。但[in]和[out]不支持包含有指针的结构体,这种情况必须使用user_check,并进行手动的验证。为了保证copy指针指向数据的安全性,它们还会和size,count,sizefun等一起使用。

其它数据类型

string和wstring表示参数是一个以NULL结束的字符串。

EDL支持用户定义的数据类型,但是不能定义在头文件中。任何使用typedef的基本类型,也都是用户定义的数据类型。有一些数据类型必须指定EDL属性,例如isptr,isary,readonly等,否则edger8r在编译时会报错。

propagate_error是OCALL的一个属性,如果使用这个属性,则enclave中的errno属性,会在OCALL返回之前被覆写为untrusted域中的errno中的值。在OCALL完成之后,无论OCALL是否成功,trusted域中的都会在OCALL完成之后更新。如果function失败了,那么errno就会检查是否出错,而如果函数成功了,那么OCALL就被允许修改errno的值。

ECALL访问/配置

默认的情况下,ECALL函数是不能直接被任何untrusted functions调用的。为了允许应用程序直接调用一个ECALL函数,则这个ECALL必须用public关键字来修饰。
Enclave配置文件是一个XML文件,它包含了用户定义的enclave参数,也是enclave项目的一部分。sgx_sign利用这个文件作为输入,来创建enclave的signature和metadata,它包括有这些项:

<EnclaveConfiguration>
	<ProdID>100</ProdID> 
	<ISVSVN>1</ISVSVN> 
<StackMaxSize>0x50000</StackMaxSize>
<HeapMaxSize>0x100000</HeapMaxSize>
<TCSNum>1</TCSNum>
<TCSPolicy>1</TCSPolicy>
<DisableDebug>0</DisableDebug>
<MiscSelect>0</MiscSelect>
<MiscMask>0xFFFFFFFF</MiscMask>
	</EnclaveConfiguration>

Enclave加载

Enclave的源代码被编译为一个共享对象,为了使用一个enclave,enclave.so应该通过调用sgx_create_enclave()函数来加载到受保护的内存中。在第一次加载一个enclave时,加载器会获取launch token并且将它保存到token参数当中,用户能够将它保存在一个文件中,并且在之后加载时,从文件中获取token。而卸载enclave则是由用户调用sgx_destory_enclave(sgx_enclave_id_t)来实现的。

#define ENCLAVE_FILE _T("Enclave.signed.so")
	sgx_enclave_id_t eid;
	sgx_status_t ret = SGX_SUCCESS; 
sgx_launch_token_t token = {0};
	int updated = 0;
//创建
sgx_create_enclave(ENCLAVE_FILE, SGX_DEBUG_FLAG, &token, &updated, &eid, NULL);
//摧毁
sgx_destroy_enclave(eid);

Untrusted/Trusted Library Functions

untrusted函数只能在应用中,也就是enclave的外部调用。这些函数包括:

  • Enclave的创建和摧毁
  • Quoting(用来确定处于SGX环境中)
  • untrusted key交换
  • 平台服务和启动控制

trusted库和enclave binary静态链接,它们只能在enclave内部使用。 Trusted Runtime System是SDK的一个关键组件,它提供enclave的入口逻辑,其他的helper函数,以及自定义的异常处理。
Trusted Service Library对数据进行保护,它包括有:

- Wrapper函数 - sealing/unsealing - Trusted平台服务函数

SDK

SGX的SDK目前提供了两种模式,hardware模式,也即在物理上拥有sgx的计算机上能够使用;而simulation则是模拟拥有sgx的计算机。在DEBUG模式中,开发者能够直接使用被签名过的enclave.signed.so,而不需要自己去进行签名。

3 Signature

在Enclave中,可信环境的建立有三个主要的部分,分别是
Measurement:当前环境下的,enclave的身份证明
Attestation:向其他部分证明自身可信
Sealing:能够在可信环境恢复时,恢复其相关的数据


Measurement

Enclave包含一个由author提供的证书,也即Enclave Signature,它能够让SGX来检测enclave文件是否被篡改了,从而证明这个enclave是可信的。但硬件只在装载的时候进行检验,因此enclave signature还会对author进行验证,它包含这些部分:
Enclave Measurement、Enclave Author的公钥、Security Verision Number、Product ID

Attestation

Attestation指的是第三方能够证实软件是在SGX平台上运行的。Intel SGX架构支持两种验证:本地验证和远程验证。

本地验证:一个enclave和另一个enclave协作,那么这两者之间就需要进行验证。enclave能够使用硬件来生成credential(report),用来发给另一个enclave进行验证。

远程验证:一个拥有enclave的应用需要使用一个平台外的服务时,能够使用enclave来制造一个report,并且将它给平台服务,来产生一个credential(quote),使用EPID技术来进行验证它来进行检测。

Sealing

在enclave被销毁时,需要识别出其中需要保护的data和state,以便在之后仍然能够在enclave中使用这些数据。这些数据只有被保存在enclave的外部。有两种情形:
Seal到当前Enclave:在enclave创建时会有一个MRENCLAVE,只有拥有相同的MRENCLAVE才能unseal
Seal到Enclave作者:在enclave创建时会有一个MRSIGNER,只有拥有相同MRSIGNER才能unseal

4 处理器特性

enclave writer需要依赖编译器和库,他无法知道生成的enclave是否使用了任何特殊的CPU拓展特性。不可信的loader可能会允许所有的特性,但是通过设置Enclave Signature Structure,是能够指定重载这部分的设置的。

在Enclave中,有一些指令是非法的,包括可能VMEXIT的,无法被软件处理的中断的,以及需要改变权限级别的,以及CPUID。

5 Power Management

现代操作系统提供的一种机制,允许应用被能耗事件通知。当平台进入S3和S4状态时,密钥会被擦除,所有的enclave会被销毁。Intel SGX并不直接提供Power down事件到enclave当中。应用可以为这些事件注册相应的回调函数,在其被调用时,将secret state保存到磁盘上。但OS不保证enclave有足够的时间去做这件事情,因此enclave最好通过power transition events对enclave state data进行周期性的保护。

6 线程相关

当多线程的程序运行时,thread Binding,TLS的使用都可能带来问题。对于enclave来说,开发者可以选择Non-Binding和Binding两种模式。Non-Binding模式会在不可信运行时,使用任意的TTC,并且使用一个root call进入enclave中,每次root call时,TLS都会被初始化;Binding模式下,不可信线程会和一个enclave中的可信线程绑定。

SGX虚拟化

KVM-SGX

虚拟机中的epc,使用sgx_vm_epc_buffer结构来管理的。其数据结构如下:

struct sgx_vm_epc_buffer {
	struct list_head buf_list;
	struct list_head page_list;
	unsigned int nr_pages;
	unsigned long userspace_addr;
	__u32 handle;
};

Kai Huang添加了sgx_vm.c。

__alloc_epc_buf:申请一个新的epc buffer,在这里对每一个页,都申请了一个iso_page。它调用sgx_alloc_vm_epc_page来完成具体的申请。这个函数定义在sgx_alloc_epc_page当中,实际上调用的就是sgx_alloc_epc_page。

__get_next_epc_buf_handle():每一个epc buffer都有一个handle,这个handle相当于一个标识,利用handle来找到对应的epc buffer,这个函数用来在申请一个epc_buf的时候,获取它的handle。  

__free_epc_buf():释放epc buffer,这里同样也要完成对应的iso_pages的释放。  

__sgx_map_vm_epc_buffer():按页实际完成按页的映射,它调用了vm_insert_pfn函数。它进而调用vm_insert_pfn_prot,这个函数位于mm/memory.c中,也即完成一个页的映射(pfn to pfn)。

在kvm_main.c中,hva_to_pfn函数被进行了修改,这是为了让pfn的映射,支持到EPC这一段内存,而epc并不是连续的,所以需要从页表中搜索到pfn。hva_to_pfn(),其参数address为host virtual address,通过一个guest页的hva,来找到对应的pfn。它调用了follow_pfn(使用user virtual address来查找一个页框)。

只有在hva_to_pfn_fast/hva_to_pfn_slow都无法找到pfn时,才会使用这种方法。因为EPC不是连续的内存段,所以会用这种方法特殊处理。

arch/x86/kvm/vmx.h/vmx.c中,做了VMX和SGX交互的修改。

首先对于一部分enclave中不允许使用的指令,例如CPUID,INVD,做了#GP处理。

vmx_exit_from_enclave():enclave中发生了VMEXIT的情况。这里使用了VM_EXIT_REASON的bit 27和GUEST_INTERRUPTIBILITY_INFO中的bit 4来表示VMEXIT的原因。

vmx_handle_exit是对exit的具体处理。利用VM_EXIT_REASON的bit 27,可以判断这个exit是在enclave当中发生的。这个函数确定exit的类型,再交由对应的handler进行处理。

QEMU-SGX

在target-i386/kvm.c当中,qemu首先为isgx定义了一个设备node:/dev/sgx,并且定义了VM中SGX的状态SGXState。在kvm.c中,定义了epc的alloc、free

#define SGX_IOC_ALLOC_VM_EPC  _IOWR(SGX_MAGIC, 0x03, struct sgx_alloc_vm_epc)
#define SGX_IOC_FREE_VM_EPC  _IOW(SGX_MAGIC, 0x04, struct sgx_free_vm_epc)

这两个宏定义,最后交给了struct中对应的handle来处理,也即ISGX驱动中对应的函数。而qemu内部的接口则是kvm_alloc_epc/kvm_free_vm_epc。kvm.c中,还定义了epc的初始化和销毁。epc的计算是在vcpus创建之前完成的,这里epc的大小被限制在256M之内,它被放置在below_4g_memory_size的位置,位于PCI的基址之下。在target-i386/cpu.c中,添加了对cpuid对应功能的支持。 在/hw/i386/acpi-build.c当中,添加了EPC的ACPI table项。



本文链接: http://home.meng.uno/articles/4b3349ff/ 欢迎转载!

© 2018.02.08 - 2020.10.14 Mengmeng Kuang  保留所有权利!

UV : | PV :

:D 获取中...

Creative Commons License