Intel SGX开发者参考书(四)—— Enclave开发基础(一)

@Intel SGX 读书笔记…

Enclave开发基础

本章节介绍以下Enclave开发工具:

  • 编写Enclave函数
  • 调用函数到Enclave内
  • 调用函数到Enclave外
  • 将Enclave与库连接起来
  • 将应用程序与不可信库连接起来
  • Enclave定义语言(EDL)语法
  • 加载和卸载Enclave

典型的Enclave开发过程包含以下步骤:

  1. 使用IDE插件向导生成Enclave项目
  2. 在EDL文件中定义不可信应用程序和Enclave之间的接口
  3. 执行应用程序和Enclave函数
  4. 构建应用程序和Enclave。在构建过程中,Edger8r Tool生成可信和不可信代理/桥函数。Enclave签名工具生成Enclave的元数据和签名。
  5. 在模拟和硬件模式下运行和调试应用程序。
  6. 准备发布应用程序和Enclave。

编写Enclave函数

从应用程序角度看,在使用不可行代理函数时,使Enclave调用(ECALL)作为任何其他函数调用出现。Enclave函数是纯C/C++函数,但有几个限制。(只支持C/C++语言)
Enclave函数可以依靠C/C++运行时库的特别版本、STL、同步和其他几个受信任的库,这些库是Intel SGX SDK的一部分。这些受信任的库是专门设计用于Enclave内部的。
用户可以编写或使用其他可信任的库,确保库遵循与内部Enclave函数相同的规则:

  1. Enclave函数不能使用所有可用的32位或64位指令。要查看Enclave内的非法指令列表,请参阅Intel SGX编程参考资料。
  2. Enclave函数只在用户模式(ring3)下运行。使用需要其他CPU特权的指令,这将造成Enclave出错。
  3. 如果被调用的函数被静态地连接到Enclave,则可以在Enclave内调用函数(该函数需要位于Enclave映像文件中)。不支持Windows动态库。

如果Enclave映像中包含构建时任何未解决的依赖项,Enclave签名过程会失败的哦。

可以使用OCALL调用Enclave之外的函数。OCALLs在Calling Functions outside the Enclave中有详细的解释。
表1 Intel SGX规则和局限的总结

功能 支持 评论
语言 部分 本机C/C++。Enclave接口函数仅限于C(不包括C++)
C/C++调用其他dll 没有 可以通过显示外部调用(OCALLs)实现
C/C++调用系统提供的C/C++/STL标准库 没有 这些库的可新版本由Intel SGX SDK提供,可以使用它们
OS API调用(比如,WIN32) 没有 可以通过OCALL实现
C++框架 没有 包括MFC,QT,Boost(部分-只要不使用Boost运行时)
调用C+++类方法 可以 包括C++类、静态函数和内联函数
内部函数 部分 仅当它们使用受支持的指令时才受支持。允许的功能包括在Intel SGX SDK里。
内联汇编 部分 与内部函数一样
模板函数 部分 仅在Enclave内部函数中支持
Ellipse (…) 部分 仅在Enclave内部函数中支持
Varargs (va_list) 部分 仅在Enclave内部函数中支持
同步 部分 Intel SGX SDK提供了一组用于同步的函数/对象:自旋锁、互斥锁和条件变量。
线程支持 部分 不支持在Enclave内创建线程。在Enclave内运行的线程是在(不可信的)应用程序中创建。自旋锁、可信互斥锁和条件变量API可用于Enclave内的线程同步。
线程本地存储(TLS) 部分 仅通过declspec(线程)、Thread_local或thread_local。没有TLS的动态分配。
动态内存分配 可以 Enclave内存是一个有限的资源。最大堆大小在Enclave创建时设置。
C++异常 但是他们对性能有影响
SEH已成 没有 Intel SGX SDK提供了一个API,允许您注册函数或异常处理程序,以处理一组有限的硬件异常。/td>

调用Enclave中的函数

成功加载Enclave后,您将获得一个Enclave ID,它是在执行ECALLs时作为参数提供的。可选择地,OCALLs可以在ECALL中执行。比如,假设你需要在Enclave中计算一些秘密,EDL文件也许就如下所示:

// demo.edl
enclave {
     
	// Add your definition of "secret_t" here
	trusted {
     
		public void get_secret([out] secret_t* secret);	
	};
	untrusted {
     
		// This OCALL is for illustration purposes only.
		// It should not be used in a real enclave, unless it is during the development phase for debugging purposes.
		void dump_secret([in] const secret_t* secret);
	};
};

有了上述EDL,sgx_edger8r会生成一个不可信地代理函数给ECALL和一个可信的代理函数给OCALL:
不可信的代理函数(被应用程序调用):

sgx_status_t get_secret(sgx_enclave_id_t eid, secret_t* secret);

可信的代理函数(被Enclave调用):

sgx_status_t dump_secret(const secret_t* secret);

生成的不可信代理函数将自动调用Enclave,并将参数传递给Enclave内的真正的可信函数get_secret。在应用程序中初始化一个Enclave:

// An enclave call (ECALL) will happen here
secret_t secret;
sgx_status_t status = get_secret(eid, &secret);

Enclave中的可信函数可以选择执行OCALL来使用可信代理dump_secret转储秘密。它将使用给定的参数从Enclave中自动调用,这些参数将由真正的不可信函数dump_secret接收。真正不可信的功能需要由开发人员实现并与应用程序连接。
确认返回值
可信和不可信的代理函数返回一个sgx_status_t类型的值。如果代理函数成功运行,它将返回SGX_SUCCESS。否则,报错。你可以参考SDK附带的示例代码,以获得正确的错误处理示例。

调用Enclave之外的函数

在某些情况下,Enclave内的代码需要调用驻留在不可信(不受保护)内存中的外部函数,以使用Enclave外的操作系统功能,例如系统调用、I/O操作等等。这种类型的函数调用称为OCALL。这些函数需要在EDL文件的不可信部分定义。
Enclave映像的加载方式与Windows OS加载dll的方式非常相似。应用程序的函数地址空间与Enclave共享,因此,Enclave代码可以间接地调用与创建Enclave地应用程序相连接地函数。不允许直接从应用程序调用函数,并且将在运行时发生异常。

包装器函数将参数从受保护(enclave)内存复制到不受保护地内存,因为外部函数无法访问受保护地内存区域。特别是,OCALL参数被复制到不可信的堆栈中。根据参数地数量,OCALL可能会在不可信域中导致堆栈溢出。这个事件的异常将触发将显示来自sgx_edger8r基于Enclave EDL文件生成的代码。然而,使用Intel SGX调试器可以很容易地检测到异常。
只有在EDL文件中为这些指针分配特殊属性时,包装器函数才会复制缓冲区(指针引用地内存)。
某些与Intel SGX SDK一起发布的受信任库提供了一个内部生成OCALLdeAPI。目前,来自sgx_tstd.lib的Intel SGX互斥锁、条件变量和CPUID API可以进行调用。类似地,受信任地支持库sgx_tservice.lib,它提供来自Plarform Services Enclave(PSE-Op)的服务,也可以进行OCALLs。使用这些API的开发人员必须首先从响应的EDL文件中导入所需的OCALL函数。否则,在构建Enclave时,开发人员将得到一个连接错误。
为了帮助识别由于缺少导入而导致的问题,Intel SGX SDK中使用的所有OCALL函数都带有后缀ocall。
比如说,下面的链接器错误说明了Enclave需要在sethread_mutex.obj中引入sgx_thread_wait_untrusted_event_ocall()和thread_set_untrusted_event_ocall(),它是sgx_tstdc.lib的一部分吗。

>sgx_tstdc.lib(sethread_mutex.obj) : error LNK2001: unresolved external symbol _sgx_thread_wait_untrusted_event_
ocall
sgx_tstdc.lib(sethread_mutex.obj) : error LNK2001: unresolved external symbol _sgx_thread_set_untrusted_event_
ocall

从不受保护的内存中访问受保护的内存将导致中止页面语义。这适用于受保护内存的所有部分,包括堆、堆栈、代码和数据。
中止页面的语义:
尝试从不存在或不允许的资源读取数据将返回所有数据(中止页)。写入不存在或不允许的物理资源的尝试将被放弃。此行为与异常类型abort有关(其他类型为Fault和Trap)。

OCALL函数有以下的局限性/规则:
OCALL函数必须是C函数,或者是带有C链接的C++函数。
在Enclave中引用数据的指针必须使用EDL文件中的指针方向属性进行注释。包装器函数将对这些指针执行浅拷贝。
Enclave内不会捕获异常。用户必须在不可信的包装器函数中处理它们。
OCALLs的原型中不能有ellipse (…)或va_list。
举例1:一个简单OCALL函数的定义
步骤一:在EDL文件中为foo添加一个声明

// foo.edl
enclave {
     
	untrusted {
     
		[cdecl] void foo(int param);
	};
};

步骤二:(可选择但是高度推荐)–编写一个可信的,用户友好的包装器。这个函数是Enclave可信代码的一部分。
以下是包装器函数ocall_foo:(Enclave.app中的函数,所以用到的foo函数需要在EDL中声明一下)

// enclave's trusted code
#include "foo_t.h"
void ocall_foo(int param)
{
     
	// it is necessary to check the return value of foo()
	if (foo(param) != SGX_SUCCESS)
		abort();
}

步骤三:编写一个不可信foo函数

// untrusted code
void foo(int param)
{
     
	// the implementation of foo
}

sgx_edger8r将生成一个不可信的桥函数,该函数将自动调用不可信函数foo。这个不可信桥和目标不可信函数是应用程序的一部分,而不是Enclave。

为Enclave开发库

可信库是指设计为与Enclave链接的静态库的属于。以下描述了可信库的特点:

  • 可信库是Intel SGX解决方案的组件。它们通常会经历比常规静态库更严格的威胁评估和审查过程。
  • 开发(或移植)受信任的库的特定目的是在Enclave中使用。因此,它不应该包含不被Intel SGX架构支持的指令。
  • 可信库API的子集也可能是Enclave接口的一部分。可以向不可信域公开的可信库接口在EDL文件中定义。如果存在,这个EDL文件是可信库的一个关键组件。
  • 可信的库可能必须与不可信库一起提供。可信库中的函数可能会在Enclave之外调用。如果平台上可用的库没有提供可信库使用的外部函数,则可信库将需要一个不可信的支持库。

总之,可信库除了包含可信的代码和数据的.lib文件,还可能包含.edl文件和不可信的.lib文件。
以下主要描述了开发可信库的过程,并概述了构建使用这种可信库的Enclave所需的主要步骤。
1.ISV提供一个可信库,包含可信的函数(没有任何边例程),必要时还提供一个EDL文件和一个不可信的支持库。要开发一个可信的库,ISV应该创建一个Enclave项目,并在Intel SGX向导中选择library选项。这将确保使用适当的设置构建库。如果可信库只提供Enclave中调用的接口,则ISV可能会从项目中删除EDL文件。如果需要,ISV应该为不可信的支持库创建一个标准的静态库项目。
2.使用库EDL文件路径和名称向Enclave EDL文件添加“from/import”语句。Import语句指出可以从Enclave外部调用库中的哪些可信函数(ECALLs),以及可以从可信库中调用哪些不可信函数(OCALLs)。你可以从可信库导入所有的ECALLs和OCALLs,或者选择它们的一个特定子集。
一个库EDL文件可以导入其他的库EDL文件来构建层次结构。
3.在Enclave构建过程中,sgx_edger8r为所有可信的和不可信的函数生成代理/桥代码。生成的代码说明了Enclave EDL文件中声明的函数,以及任何导入的可信库EDl文件。
4.可信库和可信代理/桥接函数链接到Enclave代码。

注意:如果使用通配符选项导入可信库,则生成的Enclave包含所有Ecall及其相应实现的可信桥接函数。链接器将无法优化此代码。

5.Intel SGX应用程序链接到不可信的代理/桥接代码。相似地,当使用通配符选项时,所有OCALLs的不可信桥接函数都将连接进来。

避免命名冲突

一个应用程序也许与多个Enclave一起工作。这种情况下,每个Enclave仍然是一个独立的编译单元,生成一个单独的DLL文件。
就像一般的DLL文件,Enclave应该提供一个唯一的接口,在一个不可信应用程序与几个Enclave的边例程相连接时避免命名冲突。sgx_edger8r可以防止OCALL函数之间的名称冲突,因为它会自动将Enclave名称添加到不可信的桥接函数的名称中。但是,ISV必须确保ECALL函数名在不同包之间的唯一性,以防止ECALL函数之间的冲突。
尽管拥有唯一的ECALL函数名,但在开发Intel SGX应用程序时也可能发生名称冲突。这是因为一个Enclave不能导入另一个EDL文件。当两个Enclave从可信库导入相同的ECALL函数时,两个Enclave的边例程集将包含相同的不可信的代理函数和对导入的ECALL的数据结构进行封送处理。因此,当应用程序与这两组边例程链接时,链接器将发出一个错误。要构建一个包含多个Enclave的应用程序(当这些Enclave从可信库导入相同的ECALL时),ISV必须这样做:
1.提供–use-prefix选项给sgx_edger8r,将把Enclave名称添加到不可信的代理函数名称之前。例如,当一个Enclave使用Intel SGX SDK中包含的本地认证可信库示例代码时,必须使用sgx_edger8r的–use-prefix 选项解析Enclave EDL文件。更多详情可以参看Local Attestation。
2.在不可信代码中的所有ECALL前加上Enclave名称,以匹配新的代理函数名称。

将Enclave与库连接起来

本章节主要介绍Enclave如何与下列库连接的:

  • 动态库
  • 静态库
  • 仿真库

动态库
一个Enclave DLL无论如何都不能依赖动态连接的库。 Enclave加载程序被有意设计为禁止在Enclave内动态链接库。对Enclave的保护取决于在加载时对放入Enclave的所有代码和数据的精确测量;因此,动态链接会增加复杂性,这与静态链接相比没有任何好处。

注意:如果Enclave文件有任何未解析的依赖项,则Enclave影响签名过程将失败。这意味着一个Enclave必须有一个空的IAT(Import Address Table,导入地址表)。

静态库
你可以链接到静态库,只要它们没有任何依赖关系。Intel SGX SDK提供以下可信库的集合。
表2 Intel SGX SDK中的可信库

名称 描述 评论
sgx_trts.lib Intel SGX 运行时库 当在HW模式下运行时必须链接
sgx_trts_sim.lib Intel SGX 运行时库(模拟模式) 当在模拟模式下运行时必须链接
sgx_tstdc.lib 标准C语言库(math,string等) 必须链接
sgx_tcxx.lib 标准C++库,STL 可选的
sgx_tservice.lib 数据封装/解封装(解密),可信的架构Enclave支持,Elliptic Curve Diffie-Hellman(EC DH)库等 在HW模式是必须链接
sgx_tservice_sim.lib 模拟模式下与sgx_tservice相对应的 在模拟模式下必须链接
sgx_tcryptolib 加密库 必须链接
sgx_tkey_exchange.lib 可信密钥交换库 可选择的
sgx_tprotected_fs.lib 受保护的文件系统库 可选择的
sgx_tedmm.lib 在Intel SGX2.0硬件平台上支持Intel SGX EDMM 可选择的,输入到sgx_tedmm.edl中
libsgx_tswitchless.asgx_tswitchless.lib 无切换的Enclave函数调用 可选择的
sgx_pcl.lib 启用Intel SGX受保护的代码加载程序,以实现Enclave代码机密性 可选择的

仿真库
Intel SGX SDK在模拟模式下提供仿真库运行Enclave应用程序(不需要Intel SGX硬件)。其中有一个可信的仿真库和一个不可信的仿真库。不可信仿真库提供了不可信运行时库管理Enclave与可信仿真库链接的所需的功能,包括在Enclave之外执行Intel SGX指令的模拟:ECREATE,EADD,EEXTEND,EINIT,EREMOVE和EENTER。可信仿真库主要负责仿真Intel SGX指令,这些指令可以在Enclave中执行:EEXIT、EGETKEY和EREPORT。

注意:模拟模式不需要Intel SGX在CPU上的支持。但是,处理器必须至少支持Intel Streaming SIMD Extension4.1指令。

将应用程序与不可信库链接

Intel SGX SDK提供以下不可信库的集合。
表3 Intel SGX SDK中的不可信库

名称 描述 评论
sgx_urts.lib 为应用程序提供作用去管理Enclave 必须在HW模式下运行时链接;sgx_urts.dll是Intel SGX PSW内的
sgx_urts_simd.dll uRTS库用于模拟模式 动态链接
sgx_urts_sim.lib 与模拟模式下对应于sgx_urts.lib 在模拟模式下运行时必须链接
sgx_uae_service.lib 同时提供对AEs提供的服务的Enclave和不受信任的应用程序访问 必须在HW模式下链接;sgx_uae_service.dll是包含在Intel SGX PSW中的
sgx_uae_service_sim.dll 模拟模式中不可信的AE支持库 动态链接
sgx_uae_service_sim.lib 与模拟模式中sgx_uae_service.lib对应 在模拟模式中必须链接
sgx_ukey_exchange.lib 不可信的密钥交换库(使用/MD构建) 可选择的
sgx_ukey_exchangemt.lib 不可信的密钥交换库(使用/MT构建) 可选择的
sgx_status.dlll 为应用程序签署Enclave密钥白名单证书链提供功能 可选择的
sgx_capable.dl 为应用程序查询Intel SGX设备状态和安装的PSW版本,或启用Intel SGX设备提供功能 可选择的
sgx_uprotected_fs.lib 提供对Intel 受保护文件系统库所需的Enclave之外的系统调用的实现 可选择的

加载不可信的Intel SGX DLLs
与Intel SGX PSW(sgx_urts.dll和sgx_uae_service.dll)一起提供的Intel SGX DLLs安装在系统目录中。你必须锁定Intel SGX应用程序安装目录。否则,必须显示加载这两个dll。
假设攻击者控制了安装应用程序的目录,并在该目录中插入了Intel SGX DLL的恶意副本。如果应用程序隐式加载Intel SGX DLL,则坏副本将在系统路径中的原始Intel SGX DLL之前加载。
为了确保Intel SGX应用程序正在从系统目录加载Intel SGX DLL,应用程序应该按照以下顺序显式加载这两个DLL
1.sgx_uae_service.dll
2.sgx_urts.dll

Enclave定义语言语法(EDL语法)

EDL文件用于描述在函数原型中使用的Enclave可信和不可信的函数和类型。Edger8r Tool使用该文件为Enclave导出(由ECALLs使用)和导入(由OCALLs使用)创建C包装器函数。
EDL模板

enclave {
     
	//Include files
	//Import 其他EDL文件
	//在EDL中将数据结构声明用作函数原型的参数
	trusted {
     
		//Include "enclave_t.h"头文件(如果需要的话)
		//可信函数原型
	};
	untrusted {
     
		//Include "enclave_u.h"(如果需要的话)
		//不可信的函数原型
	};
};

只有在将可信块用作库EDL时,它才是可选的,并且其他EDL文件将导入这个EDL。然而,不可信块总是可选的。
每个EDL文件都遵循这种通用格式:

enclave {
     
	// 一个EDL文件可以选择从其他EDL文件中导入函数
	from “other/file.edl” import foo, bar; // 	选择导入
	from “another/file.edl” import *; // 导入所有函数
	// 包含C头文件,这些头文件将包含在为可信和不可信例程生成的文件中
	include "string.h"
	include "mytypes.h"
	// 类型定义(struct,union,enum),可选
	struct mysecret {
     
		int key;
		const char* text;
	};
	enum boolean {
      FALSE = 0, TRUE = 1 };
	// 导出函数(ECALLs),可选为库EDL
	trusted {
     
		//Include "enclave_t.h",(如果需要的话)
		//可信函数原型
		public void set_secret([in] struct mysecret* psecret);
		void some_private_func(enum boolean b); 	// private ECALL
		(non-root ECALL).
	};
	// 导入函数(OCALLs),可选
	untrusted {
     
		//如果有包含在enclave_u.h中的头文件,那么它将被插入到不可信的头文件"untrusted.h"中
		“untrusted.h”
		//不可信函数原型
		// 本OCALL不允许再制作另一个ECALL
		void ocall_print();
		// 这个OCALL可以使ECALL起作用
		// “some_private_func”.
		int another_ocall([in] struct mysecret* 		psecret)
		allow(some_private_func);
	};
};

注释:这两种类型的C/C++注释都是有效的
举例:

enclave {
     
	include “stdio.h” // include stdio header
	include “../../util.h” /* this header 	defines some custom public
types */
};

Include Headers
Include定义类型的C头文件(C struct,unions,typedefs等);否则,如果在EDL中引用这些类型,则无法编译自动生成的代码。所有包含的头文件可以是全局的,也可以只属于可信函数或不可信函数。
全局包含的头文件并不意味着在Enclave和不可信的应用程序代码中包含相同的头文件。在下面的例子中Enclave将使用Intel SGX SDK中的stdio.h。而应用程序代码将使用主编译器附带的stdio.h。
当开发人员将现有代码迁移到Intel SGX技术时,使用Include指令非常方便,因为在这种情况下定义了数据类型。与其他IDL语言MIDL和OMGIDL类型,用户可以在EDL文件中定义数据类型,sgx_edger8r将生成一个包含数据类型定义的C头文件。有关EDL中支持的数据类型列表,请参阅Basic Typed。
语法

include "filename.h"

举例

enclave {
     
	include “stdio.h” // 全局头文件
	include “../../util.h”
	trusted {
     
		include “foo.h” // 只对可信函数所用
	};
	untrusted {
     
		include “bar.h” // 只为不可信函数所用
	};
};

关键字
下表中列出的标识符时为EDL保留的关键字。
表4 EDL保留关键字

数据类型
char short 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
union enum long
指针参数处理
in out user_check count size readonly
isptr string wstring
其他
enclave form/td> import trusted untrusted include
public allow isary const propagate_errno transition_using_threads
函数调用约定
cdecl stdcall fastcall dllimport
**基础类型** 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.

它还支持long long和64位long double。
基础数据类型可以使用C修改器进行修改:

const, *, [].

可以通过include一个C头文件来定义其他类型。
指针
EDL定义了几个可以和指针一起使用的属性:

in, out, user_check, string, wstring, size, count,
isptr, readonly.

其中的每一个都将在下面的进行解释。

注意:本主题中解释的指针属性仅适用于ECALL和OCALL函数参数,而不适用于ECALL或OCALL函数返回的指针。因此,ECALL或OCALL函数返回的指针不会被边例程检查,必须由Enclave和应用程序代码进行验证。

指针处理
指针应该明确地用指针方向属性in、out或user_check属性修饰。[in]和[out]作为方向属性。

  • [in]——当为指针参数指定[in]时,参数从调用过程传递到被调用过程。对于ECALL,in参数从应用程序传递传递到Enclave,对于OCALL,参数从Enclave传递到应用程序。
  • [out]——当为指针参数指定[out]时,参数将从被调用的过程返回到调用过程。在ECALL函数中,out参数从Enclave传递到应用程序,OCALL函数将它从应用程序传递到Enclave。
  • 可以组合[in]和[out]属性。在这种情况下,参数是双向传递的。
    方向属性指示可信的边例程(可信桥和可信代理)复制指针指向的缓冲区。为了复制缓冲区内容,可信的边缘例程必须知道需要复制多少数据。由于这个原因,方向属性后面通常跟着一个size或count修饰符。如果既不提供这些也不提供指针为NULL,则受信任的边例程假设count为1.在复制缓冲区时,可信桥必须避免覆盖ECALL中的Enclave内存,可信代理必须避免泄露OCALL中的机密。为了实现这个目标,作为ECALL参数传递的指针必须指向不可信的内存,而作为OCALL参数传递的指针必须指向可信的内存。如果这些条件不满足,可信桥和可信代理将分别在运行时报错,ECALL和OCALL函数将不会执行。
    你可以使用方向属性来进行交易保护。否则,你必须使用下面描述的user_check属性,并在使用它之前验证通过只从不可信的内存中获得的数据,因为指针指向的内存可能会发生意外变化,因为它存储在不受信任的内存中。但是,方向属性对包含指针的结构没有帮助。在这个场景中,你必须自己验证和复制缓冲区内容,如果需要,还必须递归地进行。或者,您可以定义一个可以深度复制的结构。有关更多信息,请参见Structure Deep Copy。

举例

enclave {
     
	trusted {
     
		public void test_ecall_user_check([user_check] int * ptr);
		public void test_ecall_in([in] int * ptr);
		public void test_ecall_out([out] int * ptr);
		public void test_ecall_in_out([in, out] int * ptr);
	};
	untrusted {
     
		void test_ocall_user_check([user_check] int * ptr);
		void test_ocall_in([in] int * ptr);
		void test_ocall_out([out] int * ptr);
		void test_ocall_in_out([in, out] int * ptr);
	};
};

不支持的语法:

enclave {
     
	trusted {
     
		// 不允许没有方向属性或"user_check"的指针
public void test_ecall_not(int * ptr);
		// 函数指针是不允许的
		public void test_ecall_func([in]int (*func_ptr)());
};
};

在上面的例子中:
ECALL:

  • [user_check]:在test_ecall_user_check函数中,指针ptr将不会被验证;您应该自己验证传递给可信函数的指针。ptr指向的缓冲区也不会被复制到内部缓冲区。
  • [in]:在test_ecall_in函数中,将在Enclave内分配与’ptr’(int)的数据类型相同大小的缓冲区。ptr指向的内容(一个整数值)将被复制到内部新分配的内存中。在Enclave内执行的任何更改对不可信的应用程序都是不可见的。
  • [out]:在test_ecall_out函数中,将在Enclave内分配一个大小与’ptr’(int)的数据类型相同的缓冲区,但是ptr指向的内容不会复制一个整数值。相反,它将被初始化为零。在可信函数返回后,Enclave内的缓冲区将被复制到ptr指向的外部缓冲区。
  • [in,out]:在test_ecall_in_out函数中,将在Enclave内分配大小相同的缓冲区,ptr指向的内容(一个整数值)将复制到这个缓冲区中。返回后,Enclave内的缓冲区将被复制到外部缓冲区。

OCALL:

  • [user_check]:在test_ocall_user_check函数中,指针ptr将不会被验证;ptr指向的缓冲区不会复制到外部缓冲区。此外,如果ptr指向Enclave内存,则应用程序不能读取/修改ptr指向的内存。
  • [in]:在test_ocall_in函数中,将在"应用程序"端(不可信端)分配与ptr(int)数据类型大小相同的缓冲区。ptr指向的内容(一个整数值)将被复制到外部新分配的内存中。应用程序执行的任何更改在Enclave内都不可见。
  • [out]:在test_ocall_out函数中,将在应用程序(不可信端)分配与ptr(int)数据类型大小相同的缓冲区,并将其内容初始化为零。在不可信的函数返回之后,Enclave外部的缓冲区将被复制到ptr所指向的Enclave缓冲区中。
  • [in,out]:在test_ocall_in_out函数中,将在应用程序端分配大小相同的缓冲区,ptr指向的内容(一个整数值)将复制到这个缓冲区。返回后,Enclave外部的缓冲区将被复制到Enclave内部的缓冲区中。

下表总结了使用in/out属性时包装器函数的行为:
表5 使用in/out属性时包装器函数的行为

ECALL OCALL
user_check 指针没有被选中。用户必须执行检查和/或复制 指针没有被选中。用户必须执行检查和/或复制
in 从应用程序复制到Enclave中的缓冲区。然后,更改将只影响Enclave内的缓冲区。安全但缓慢。 从Enclave复制到应用程序的缓冲区。如果指针指向Enclave数据,则必须使用
out 可信包装器函数将分配一个缓冲区供Enclave使用。返回时,这个缓冲区将被复制到原始缓冲区。 不可信的缓冲区将由可信的包装器函数复制到Enclave中。安全但缓慢。
in,out 结合in和out的行为。数据来回复制 与ECALL相同

EDL不能分析C头文件中的C类型文件和宏。如果指针类型别名化到没有星号(*)的类型/宏,则EDL解析器可能报告错误或没有正确复制指针的数据。
在这种情况下,使用[isptr]属性声明类型,以指示它是指针类型。有关更多信息,请参见User Defined Data Types。
举例:

// Error, PVOID is not a pointer in EDL
void foo([in, size=4] PVOID buffer);
// OK
void foo([in, size=4] void* buffer);
// OK, “isptr” indicates “PVOID” is pointer type
void foo([in, isptr, size=4] PVOID buffer);
// OK, opaque type, copy by value
// Actual address must be in untrusted memory
void foo(HWND hWnd);

ECALLs中的指针处理
在ECALLs中,可信桥接器检查封送处理结构是否与Enclave内存重叠,并自动在可信堆栈上分配空间来保存该结构的副本。然后检查指针参数的全范围不与Enclave内存重叠。当一个指向具有in属性的不可信内存的指针被传递给Enclave时,可信桥分配Enclave内部的内存,并将指针指向的内存从外部复制到Enclave内存。 当指向具有out属性的不可信内存的指针被传递给Enclave时,可信桥将在可信内存中分配一个缓冲区,将缓冲区内容归零以清除以前的任何数据,并将指向此缓冲区的指针传递给可信函数。在可信函数返回后,可信桥将可信缓冲区的内容复制到不可信内存中。 在in和out属性结合时,可信的桥接器在Enclave中分配内存,复制之前调用内存缓冲区的可信函数,一旦可信函数返回,可信桥副本的内容传递到不可信的内存缓冲区中。输出的数据量与输入的数据量相同。

注意:当带有out属性的指针参数的ECALL返回时,可信桥总是将数据从Enclave内存中的缓冲区复制到外部缓冲区。失败时必须清楚缓冲区中的所有敏感数据。

在可信桥返回之前,它释放在ECALL函数开始处分配的所有可信堆内存,这些内存用于指针参数和一个方向属性。试图在可信桥返回后使用它分配的缓冲区会导致未定义的行为。
OCALLs中的指针处理
对于OCALLs,可信代理分配堆栈外部的内存来传递封送处理结构,并检查指针参数及其全范围是否在Enclave内。当使用in属性指向可信内存的指针从Enclave(OCALL)传递时,可信代理将分配Enclave外部的内存,并将指针指向的内存从Enclave内部复制到不可信内存。 当带有out属性的可信内存指针从Enclave(OCALL)传递时,可信代理在不可信堆栈上分配一个缓冲区,并将该缓冲区的指针传递给不可信函数。在不可信的函数返回后,可信的代理将不可信缓冲区的内容复制到可信的内存中。 当in和out属性结合时,可信代理分配Enclave外内存,调用不可信函数之前复制不可信内存中的数据,不可信函数返回后,可信代理将不可信缓冲区的内容复制到可信内存中。输出的数据量与输入的数据量相同。
当可信代理函数返回时,它将释放OCALL函数开始时分配给指针参数的所有不可信堆栈内存。试图在可信代理返回后使用它分配的缓冲区将导致未定义的行为。
属性:user_check
在某些情况下,由direction属性施加的限制可能不支持跨Enclave边界进行数据通信的应用程序需求。例如,缓冲区可能太大,无法装入Enclave内存,需要将其分割成更小的块,然后在一系列ECALL中处理这些块,或者应用程序可能需要将一个指向可信内存(Enclave上下文)的指针作为ECALL参数传递。为了支持这些特定的场景,EDL语言提供了user_check属性。使用user_check属性声明的参数不会经历[in]和[out]属性所描述的任何检查。但是,你必须了解与将指针传入和传出Enclave(特别user_check属性)相关的风险。你必须确保所有指针检查和数据复制都正确完成,否则会危机Enclave机密。
缓冲区大小的计算
使用这些属性计算缓冲区大小的通用公式:
Total number of bytes = count * size

  • 上面的公式适用于指定了count和size时
  • 如果指针参数没有指定count,则假定他等于1,那么总字节数等于size
  • 如果没有指定size,则使用上面的公式计算缓冲区大小,其中size时sizeof(由指针指向的元素)

属性:size
size属性用于根据direction属性([in]/[out])(当没有指定count属性时)指示用于复制的缓冲区的大小(以字节为单位)。这个属性是必需的,因为可信桥需要知道整个缓冲区作为一个指针传递,以确保它不重叠Enclave内存,并将缓冲区的内容以不可信的内存复制到可信的内存和/或,反之亦然根据方向属性。size可以是一个整数常量,也可以是函数的一个参数。size属性通常用于void指针。
举例:

enclave{
     
	trusted {
     
		// Copies '100' bytes
		public void test_size1([in, size=100] 				void* ptr, size_t len);
		// Copies ‘len’ bytes
		public void test_size2([in, size=len] void* ptr, size_t len);
	};
};

不支持的语法:

enclave{
     
	trusted {
     
		// size/count attributes must be used with pointer direction ([in, out])
		void test_attribute_cant([size=len] void* ptr, size_t len);
	};
};

属性:count
count属性用于指示指针所指向的sizeof元素块,其字节数取决于direction属性,用于复制。count和size属性修饰符的作用相同。可信桥或可信代理复制的字节数是count和参数指向的数据类型的大小的乘积。count可以是一个整数常量,也可以是函数的一个参数。count和size属性修饰符也可以组合使用。在这种情况下,可信的边缘例程将复制一些字节,这些字节是EDL文件中的函数声明中指定的count和size参数的乘积。(size*count)
举例:

enclave{
     
	trusted {
     
		// Copies cnt * sizeof(int) bytes
		public void test_count([in, count=cnt] int* ptr, unsigned cnt);
		// Copies cnt * len bytes
		public void test_count_size([in, count=cnt, size=len] int* ptr, unsigned cnt, size_t len);
	};
};

Strings
属性string和wstring分别表示参数是NULL终止的C字符串或NULL终止的wchar_t字符串。为了防止“先检查,再使用”类别的攻击,可信的边例程首先在不可信的内存中操作,以确定字符串的长度。一旦将字符串复制到Enclave中,可信的桥明确地将NULL终止字符串。在可信内存中分配的缓冲区大小与第一部中确定的长度以及字符串终止字符的大小有关。

注意:使用string和wstring属性的时候有一些限制:

  • string和wstring不能与其他属性合用,比如size或count
    sting和wstring不能与out属性单独只用。但是,string和wstring与in和out一起是可以的。
    string只能用作char指针;但是wstring只能用作wchar_t指针

举例:

enclave {
     
	trusted {
     
		// Cannot use [out] with "string/wstring" alone
		// Using [in] , or [in, out] is acceptable
		public void test_string([in, out, string] char* str);
		public void test_wstring([in, out, wstring] char* wstr);
		public void test_const_string([in, string] const char* str);
};
}

不支持的语法:

enclave {
     
	include "user_types.h" //for typedef void const * pBuf2;
	trusted {
     
		// string/wstring attributes must be used with pointer direction
		void test_string_cant([string] char* ptr);
		void test_string_cant_usercheck([user_check, string] char*
ptr);
		// string/wstring attributes cannot be used with [out] attribute
		void test_string_out([out, string] char* str);
		// string/wstring attributes musted be used for char/wchar_t pointers
		void test_string_out([in, string] void* str);
	};
};

在第一个示例中,当将string属性用于test_string函数时,使用strlen(str)+1作为将字符串复制到Enclave内外的大小。额外的字节用于空终止。
在test_wstring函数中,将使用wsclen(str)+1(双字节单位)作为将字符串复制到Enclave内外的大小。
const关键字
EDL语言接受与C语言标准中的const关键字含义相同的const关键字。但是,在EDL语言中对这个关键字的支持是有限的。它只能与指针一起使用,并作为最外面的限定词。这满足了Intel SGX中最重要的用途,即检测const指针(指向const数据的指针)与out属性之间的冲突。C语言标准中支持的其它形式的const关键字在EDL语言中不受支持。

如有误,请指正!感激!

你可能感兴趣的:(sgx,sgx)