Enclave开发基础

文章目录

    • 1.写一个Enclave函数
    • 在Enclave 内部调用函数
    • 检查返回值
    • 在Enclave外的函数调用
    • Enclave Definition Language Syntax
    • 指针
      • CAUTION
      • 指针处理(pointer Handling)
      • 举例
    • CUTION
    • ECalls中的指针处理
      • 注意点
    • 跳过Ocall 部分
    • 属性user_check
  • 第二部分
    • buffer计算
      • 属性:size
      • 属性:count
    • 字符串
    • const 关键字
    • 数组
    • 预处理能力(Preprocessor Capability)
    • Importing EDL Libraries
    • 授权访问Ecalls函数
    • Enclave Configuration File
    • Enclave 项目配置
    • 加载Enclave 或者卸载Enclave
    • 处理电源事件
    • 问题补充
      • 什么是宏定义(宏替换)
      • 什么条件编译
      • C++中const 的使用

1.写一个Enclave函数

  • 从引用程序的角度讲,使用Enclave函数,和一般的C/C++函数类似,然而,Enclave 函数与普通函数相比带有一些限制。
  • Enclave 函数只能使用c/c++编写,不支持其他语言。
  • enclave 函数依赖C/C++运行时库,STL(Standard Template Library)的特殊版本 ,intel sgx sdk 提供的库函数,这些库专门设计用于Enclave 函数的内部使用。
  • 用户可以编写和使用其他的可信库,但是要遵循内部Enclave函数的相同规范。
  • 如果在使用Enclave 函数包含一些无法解析的依赖,在Enclave 的 签名过程(signing process ) 将会失败。

在Enclave 内部调用函数

在Enclave 被成功加载后,会返回一个enclave ID, 作为一个Ecall被调用时的参数。可以在Ecall函数内,执行Ocall。举例,假设你需要在Enclave中计算某个Secret。

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生成可信代理函数

With the above EDL, the sgx_edger8r will generate an untrusted proxy function for the ECALL and a trusted proxy function for the 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。

在应用程序中启用Ecall

sgx_status_t status = get_secret(eid, &secret);

在Enclave 中的可信函数将选择性的使用可信代理函数dump_secret() 转储密码,他将会自动在Encalve外面调用dump_secret,并且参数将会传递给dump_secret函数。
真正的不可信函数需要被开发实现,链接到应用程序。

检查返回值

生成的代理函数的返回类型sgx_status_t,如果代理函数成功运行,它将返回SGX_SUCCESS。否则返回ErrorCode

在Enclave外的函数调用

在某些情况下,在Enclave 内代码需要调用在不可信内存中的外部函数,比如:使用在Enclave外面的操作系统的能力,系统调用,I/O操作等等。这种类型的函数称为Ocall函数。
这些函数需要在不受信任部分的EDL文件中声明 。

The enclave image is loaded very similarly to how Linux* OS loads shared objects. The function address space of the application is shared with the enclave so the enclave code can indirectly call functions linked with the application that created the enclave. Calling functions from the application directly is not permitted and will raise an exception at runtime.

Ocall 函数有着以下的规则、限制:

  • ocall 函数必须是C函数,或者C++ functions with C linkage。
  • 引用Enclave 中的数据的指针,必须带有方向注解,封装函数将会对这些指针进行浅拷贝。
  • 异常不会在Enclave内捕获,用户必须在不可信的封装函数中处理。

举例:oCall函数的定义
步骤一:在EDL文件中,声明foo

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

第二步骤(可选但强烈建议):写一个可信的用户友好的包装器函数,这个函数是enclave’s的可信代码

// 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 Definition Language Syntax

EDL 文件的目的是为了描述可信函数和不可信函数,和被在函数原型中使用的数据类型。Edger8r Tool 使用这个文件创建代理函数
原文

Enclave Definition Language Syntax
Enclave Definition Language (EDL) files are meant to describe enclave trusted and untrusted functions and types used in the function prototypes. Edger8r Tool uses this file to create C wrapper functions for both enclave exports (used by ECALLs) and imports (used by OCALLs).

EDL Template

enclave {
//Include files
//Import other edl files
//Data structure declarations to be used as parameters of the
//function prototypes in edl
trusted {
//Include header files if any
//Will be includedd in enclave_t.h
//Trusted function prototypes
};
untrusted {
//Include header files if any
//Will be included in enclave_u.h
//Untrusted function prototypes
};
};

当在被用于库EDL文件,并且这个EDL文件将被其他DEL文件导入时 ,此时,可信模块才是非必须的,但是不可信模块总是可选的。

每一个EDL的通用格式

enclave {
// An EDL file can optionally import functions from
// other EDL files
from “other/file.edl” import foo, bar;  // selective importing
from “another/file.edl” import *;       // import all functions
// Include C headers, these headers will be included in the
// generated files for both trusted and untrusted routines
include "string.h"
include "mytypes.h"
// Type definitions (struct, union, enum), optional
struct mysecret {
int key;
const char* text;       
};
enum boolean { FALSE = 0, TRUE = 1 };
// Export functions (ECALLs), optional for library EDLs
trusted { 
//Include header files if any
//Will be included in enclave_t.h
//Trusted function prototypes
public void set_secret([in] struct mysecret* psecret);
void some_private_func(enum boolean b); // private ECALL
(non-root ECALL).
};
// Import functions (OCALLs), optional
untrusted {
//Include header files if any
//Will be included in enclave_u.h
//Will be inserted in untrusted header file
“untrusted.h”
//Untrusted function prototypes
// This OCALL is not allowed to make another ECALL.
void ocall_print();
// This OCALL can make an ECALL to function
// “some_private_func”.
int another_ocall([in] struct mysecret* psecret)
allow(some_private_func);
};
};

文件头Include Headers
当在EDL中引用了这些类型,必须包括定义类型的C头文件(c struct ,union ,typedef等),如果不则无法编译自动生成的代码。 包含的头文件可以是全局的,也可以只属于受信任的函数或不受信任的函数。
全局包含的头文件并不意味着Enclave和不受信任的应用程序代码中包含相同的头文件。
在以下示例中,安全区将使用英特尔(R)Software Guard Extensions SDK中的stdio.h。 然而应用程序代码将使用主机编译器附带的stdio.h。

Using the include directive is convenient when developers are migrating existing code to the Intel SGX technology, since data types are defined already in this case. Similar to other IDL languages like Microsoft* interface definition language (MIDL*) and CORBA* interface definition language (OMGIDL), a user can define data types inside the EDL file and sgx_edger8r will generate a C header file with the data type definitions.
当开发人员将现有代码迁移到IntelSGX技术时,使用include指令非常方便,由于在案例中已经定义了数据类型。

Example
enclave {
include “stdio.h”      // global headers
include “../../util.h”
trusted {
include “foo.h”   // for trusted functions only
};
untrusted {
    include “bar.h”   // for untrusted functions only
   };
 };

Keywords
Enclave开发基础_第1张图片
Enclave开发基础_第2张图片
基本数据类型
EDL supports the following basic types:

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

可以通过包含C头文件来定义其他类型
Structures, Enums and Unions
基本类型和用户定义的数据类型可以在结构/联合中使用,它与标准的语法有以下的j几点不同点

Unsupported Syntax:
enclave{
// 1. Each member of the structure has to be
// defined separately 结构体的每一个成员必须分开定义
struct data_def_t{
int a, b, c; // Not allowed
// It has to be int a; int b; int c;
};
// 2. Bit fields in structures/unions are not allowed. 在structures/unions点域是不被允许的
struct bitfields_t{
short i : 3;
short j : 6;
short k : 7;
};
//3. Nested structure definition is not allowed 嵌套结构是不允许
struct my_struct_t{
		int out_val;
		float out_fval;
		struct inner_struct_t{
		int in_val;
		float in_fval;
		};
	};
};
Valid Syntax:
enclave{
	include "user_types.h" //for ufloat: typedef float ufloat
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
};
union union_foo_t {
uint32_t union_foo_0;
uint32_t union_foo_1;
uint64_t union_foo_3;
};
trusted {
public void test_char(char val);
public void test_int(int val);
public void test_long(long long val);
public void test_float(float val);
public void test_ufloat(ufloat val);
public void test_double(double val);
public void test_long_double(long double val);
public void test_size_t(size_t val);
public void test_wchar_t(wchar_t val);
public void test_struct(struct struct_foo_t val);
public void test_struct2(struct_foo_t val);
public void test_enum(enum enum_foo_t val);
public void test_enum2(enum_foo_t val);
public void test_union(union union_foot_t val);
public void test_union2(union_foo_t val);
};
};

指针

在EDL指针可以使用的几个属性。
in, out, user_check, string, wstring, size, count,
isptr, readonly

CAUTION

本主题中解释的指针属性仅适用于eCall和OCALL函数参数,而不是应用于eCall或OCALL函数返回的指针。因此,eCall或OCALL函数返回的指针不被边缘例程(edge-routines)检查,必须由Enclave和应用程序代码进行验证。

指针处理(pointer Handling)

  1. 指针必须明确的指明方向属性in /out/user_check

`【in】和【out】作为方向属性

  • [in]-当为指针参数指定[in]时,参数将从调用过程传递到被调用过程。对于ECall,in参数从应用程序传递到Enclave,对于OCALL,参数从Enclave传递到应用程序。
  • [out] 调用过程与上面的相反
  • [in]和[out]属性可以合并。在这种情况下,参数是双向传递的。
  • 方向属性告诉了边缘例程( edge-routines (trusted bridgeand trusted proxy))去复制被指针制定的缓存。
  • 为了复制缓存内容,可信的边缘例程必须去知道多少数据去复制。因此,方向属性通常带有size or count 修饰符
    如果size 或者count 都没有提供,或者指针是NULL ,通常边缘例程认为count =1。
  • 当缓存buffer被复制,可信桥必须避免覆盖掉在Ecall中Enclave 内存,可信代理必须避免在OCALL中泄漏机密。
  • 为了实现这个目标,作为ECall参数传递的指针必须指向不可信内存,而作为OCALL参数传递的指针必须指向可信内存。如果上述条件没有被满足的话,可信桥和可信代理将会各自报告一个错误,并且Ecall和Ocall 函数不会被运行。
    使用方向属性是用性能来换取保护性。否则的话,**必须是用user_check属性并且在使用之前必须通过指针验证从不可信内存中获取的 数据。**因为它存储在不受信任的内存中,所以指针指向的内存可能会意外更改。
  • 注意点
    但是,方向属性不能用于包含指针的结构。在这个场景中,您必须自己验证和复制缓冲区内容,如果需要的话,可以递归地复制。(However, the direction attribute does not help with structures that contain pointers. In this scenario, you have to validate andcopy the buffer contents,recursively if needed, yourself.

举例

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);
       
};  
....
}

说明:

  • 【user_check】在函数test_eCall_user_check中,指针ptr不会被验证;应该验证传递给受信任函数的指针。 PTR指向的缓冲区不会复制到缓冲区内部。
  • 【in】在函数test_eCall_in中,与‘ptr’(Int)数据类型相同大小的缓冲区将在Enclave内分配,ptr指向的内容(一个整数值)将被复制到内部新分配的内存中。在Enclave内执行的任何更改对不受信任的应用程序都是不可见的。
  • 【out】在函数test_eCall_out中,与‘ptr’(Int)的数据类型相同的缓冲区将在飞地内分配。但是ptr所指向的内容,一个整数值将不会被复制。相反,它将被初始化为零。在可信函数返回后,Enclave内的缓冲区将被复制到ptr所指向的外部缓冲区中。
  • 【in,out】在函数test_eCall_in_out中,将在Enclave内分配大小相同的缓冲区,PTR指向的内容(一个整数值)将复制到此缓冲区中,返回后,飞地内的缓冲区将被复制到外部缓冲区。
enclave{
...
	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);
	 };
};
  • 【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][in,out]:在函数test_ocall_in_out中,将在应用程序端分配大小相同的缓冲区,ptr指向的内容(一个整数值)将复制到该缓冲区。返回后,Enclave外部的缓冲区将被复制到内部Enclave缓冲区中。

CUTION

EDL无法分析C在头文件中定义的别名和宏。如果指针类型别名为没有星号的类型/宏。EDL解析器可能报告错误或不正确地复制指针的数据。
在这种情况下,使用[isptr]属性声明类型,以表明它是撇号类型。有关更多信息,请参见用户定义的数据类型。

举例

// OK
void foo([in, size=4] void* buffer);
// OK, “isptr” indicates “PVOID” is pointer type
void foo([in, isptr, size=4] PVOID buffer);
//

ECalls中的指针处理

In ECALLs, the trusted bridge checks that the marshaling structure does not overlap with the enclave memory, and automatically allocates space on the trusted stack to hold a copy of the structure.
然后,它将检查指针参数及其全部范围是否与Enclave内存重叠。

  • 当带in属性的不可信内存的指针被传递给Enclave时,tursted bridge信任桥将在Enclave内分配内存,并将指针指向的内存从外部复制到Enclave存储。
  • 当带out属性的不可信内存的指针被传递给Enclave时,信任桥将在可信内存中分配一个缓冲区,将缓冲区内容归零以清除任何先前的数据。并将指向此缓冲区的指针传递给受信任的函数。当可信函数返回后,信任桥将受信任缓冲区的内容复制到不可信内存中。
    -当将输入和输出属性组合时,信任桥将在飞地内分配内存,在调用受信任的函数之前,在可信内存中复制缓冲区,并在受信任函数返回时,受信任桥将受信任缓冲区的内容复制到不可信内存中。
  • 在可信桥返回前,他会释放所有在Ecall函数开始使用时带有方向属性的可信堆内存内存,试图使用那些可信桥返回结果后的函数将导致未定义行为。

注意点

当带有带out属性的指针参数的eCall返回时,受信任的桥总是将数据从飞地内存中的缓冲区复制到外部缓冲区。在发生故障时,必须清除该缓冲区中的所有敏感数据。

跳过Ocall 部分

属性user_check

总结下文:user_check 是为了一些特殊的场景使用的属性,
举例:方向属性施加的限制可以不支持跨越飞地的数据通信的应用程序需求边界。例如数据太大一个Enclave 放不下,则可以将其分解放入不同Enclave中。

Attribute: user_check

In certain situations, the restrictions imposed by the direction attribute may
not support the application needs for data communication across the enclave
boundary. For instance, a buffer might be too large to fit in enclave memory
and needs to be fragmented into smaller blocks that are then processed in a
series of ECALLs, or an application might require passing a pointer to trusted
memory (enclave context) as an ECALL parameter.
To support these specific scenarios, the EDL language provides the user_
check attribute. Parameters declared with the user_check attribute do not undergo any of the checks described for [in] and [out] attributes. However, you must understand the risks associated with passing pointers in and out the enclave, in general, and the user_check attribute, in particular. You must ensure that all the pointer checking and data copying are done correctly or risk compromising enclave secrets.

第二部分

buffer计算

一个广义公式使用属性计算缓存区大小
总共的字节数量=count*size;
要点:

  1. 当count没有被指定时,他将被认为count=1,字节总共为size*1
  2. 当size 没有指定时,缓存区大小的计算中size=sizeof(由指针指向的元素(element pointed by the pointer))

属性:size

size 用于指示缓存区大小,以字节为单位,This attribute is needed because the trusted bridge needs to know the whole range of the buffer passed as a pointer to ensure it does not overlap with the enclave memory, and to copy the contents of the buffer from untrusted memory to trusted memory and/or vice versa depending on the direction attribute. 大小可以是整数常量,也可以是函数的参数之一。 size属性通常用于void指针。 注解:由于指针一般都有数据类型(除了void没有类型大小可定 )所以count* sizeof(指针的类型),所以只需要指定一般指定count 即可(可以想象int a[5]数组的指针,指定。。。。。)。

// Example
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);
	};
};

属性:count

Count和size属性修饰符具有相同的用途,信任桥和可信代理复制的字节,取决于参数指向的数据类型的size和count的乘积,计数可以是整数常量,也可以是函数的参数之一。
大小和计数属性修饰符也可以组合在一起。在这种情况下,受信任的边缘例程将复制许多字节,这些字节是edl文件中函数声明中指定的计数和大小参数(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);
	};
};

字符串

属性string和wstring表明参数是以以‘\0’结尾的字符串(string和wstring),为了防止“先检查,然后使用”类型的攻击,第一步受信任的边缘例程在不受信任的内存中操作,以确定字符串的长度。第二步:。。。。,
在可信内存中分配的缓冲区的大小由第一步中的长度和 字符串终止字符(\0)决定。

NOTE

string 和wstring 属性有一些限制

  • 不能和一些其他的修饰符同时使用。例如:size、count 不能和string/wstring 同时使用。
  • string 和wstring 不能和out单独同时使用,但是in,out可以和wsting /string 同时使用。
  • string只能用于char指针,而wsting只能用于wchar_t指针

举例
正确的使用方式

Example
	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);
	};
};
  • string 属性用于函数test_string,Eclave中的大小是, strlen(str)+1 ,1是null termination(/0)
  • test_wstring中的在Enclave中的字符缓存的大小是: wcslen(str)+1

错误使用方式

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);
	};
};

const 关键字

EDL语言中的const 关键字与C标准语言中的const 含义是一致的。但是,在edl语言中,对这个关键字的支持是有限的。它只能与指针一起使用,并作为最外层的限定符使用。This satisfies the most important usage in Intel(R) SGX, which is to detect conflicts between const pointers (pointers to const data) with the out attribute. Other forms of the const keyword supported in the C standard are not supported in the EDL languag EDL 中const的唯一作用。

数组

  • EDL 语言支持多维的、固定大小的,在数据结构定义和参数声明中定义的数组。
  • 数组应该类似于指正,使用属性【in】【out】或者【user_check】

注意点NOTE

  • 数组不能使用size/count
  • const 不能用于数组
  • 不支持0大小的数组或者可变数组
  • 不支持指针数组

Example

enclave {
	include "user_types.h"  //for uArray - typedef int uArray[10];
	trusted {
		public void test_array([in] int arr[4]);
		public void test_array_multi([in] int arr[4][4]);
	};
};
Unsupported Syntax:
enclave {
	include "user_types.h"  //for uArray - typedef int uArray[10];
	trusted {
		// Flexible array is not supported
		public void test_flexible(int arr[][4]);
		// Zero-length array is not supported.
		public void test_zero(int arr[0]);
	};
};

isary :被用于指定指定函数参数,是一个用户定义的类型数组(数组类型)

用户定义的数组类型

EDL支持用户自定义的数据类型,但是应该定义在头文件中,一些用户数据类型需要使用特殊的EDL属性进行注释,例如:isptr,isary 和 readonly,当用户定义的数据类型,缺少这些属性时,编译器将 sgx_edger8r生成的代码中发出编译错误。

  • 当存在一个用户定义的指针数据类型时,isptr 用于指定用户定义的是一个指针。
  • 同理:数组使用isary
  • 当ECall 或者Ocall 使用,用户自定义的const 数据类型 时,参数需要被注明是readonly 属性。

注意点:

  • isptr、isary和readonly只能用于修饰用户定义的数据类型不要将它们用于任何基本类型、指针或数组
  • readonly只能与isptr属性一起使用。其他使用方式都是不行的。
Example
enclave {
	include "user_types.h"  // for typedef void * pBuf;
	  // and typedef void const * pBuf2;
	  // and typedef int uArray[10];
		trusted {  
			public void test_isptr( [in, isptr,  size=len] pBuf pBufptr, size_t len);
			public void test_isptr_readonly( [in, isptr, readonly, size=len] pBuf2 pBuf2ptr, size_t len);
			public void test_isary([in, isary] uArray arr);
		};
};
Unsupported Syntax:
		enclave {
		include "user_types.h"  //for typedef void const * pBuf2;
		  // and typedef int uArray[10];
		trusted {
			// Cannot use [out] when using [readonly] attribute
			void test_isptr_readonly_cant(
			[in, out, isptr, readonly,  size=len] pBuf2 pBuf2ptr, size_t len);
			// isptr cannot be used for pointers/arrays
			public void test_isptr_cant1( [in, isptr,  size=len] pBuf* pBufptr, size_t len);
			public void test_isptr_cant2( [in, isptr,  size=len] void* pBufptr, size_t len);
			// User-defined array types need "isary"
			public void test_miss_isary([in] uArray arr);
			// size/count attributes cannot be used for user-defined array types
			public void test_isary_cant_size( [in, size=len] uArray arr, size_t len);,
			// isary cannot be used for pointers/arrays
			public void test_isary_cant( [in, isary] uArray arr[4]);
		};
};

In the function test_isptr_readonly, pBuf2 (typedef void const * pBuf2) is a user defined pointer type, so isptr is used to indicate that it is a user defined type. Also, the pBuff2ptr is readonly, so you cannot use the out attribute.

预处理能力(Preprocessor Capability)

EDL语言支持EDL语言支持宏定义条件编译指令。为了提供此功能,sgx_edger8r首先使用编译器预处理器解析EDL文件。一旦所有的预处理器标记都被翻译完,sgx_edger8r将结果文件解析为常规EDL语言。意味着开发人员可以定义简单的宏,并使用条件编译指令轻松地从Enclave生产包中,删除调试和测试功能。

  • sgx_edger8r不将宏定义从EDL文件传播到生成的边缘例程中.所以你需要在EDL文件和编译器参数或其他源文件中复制宏定义(在EDL文件中和其他源程序中重复定义宏)
#define SGX_DEBUG
enclave {
		trusted {
		// ECALL definitions
	}
	untrusted {
		// OCALL definitions
		#ifdef SGX_DEBUG
		void print([in, string] const char * str);
		#endif
	}
}

Importing EDL Libraries

你可以实现导入导出外部可信库的功能,类似于在可不信域中的静态库。使用EDL导入库文件机制来实现。
使用EDL关键字"from”和“import”将EDL库文件添加到Enclave EDL文件中。

  • From:指定了EDL库文件的具体位置,相对路径和完全路径都是可行的,相对路径相对于EDL文件的位置。建议使用不同的名称来区分库EDL文件和Enclave EDL文件。
  • import:指定导入的具体的函数,* 为指定的所有函数,超过一个可以使用commas(逗号)逗号进行分割。
  • Syntax
    from “lib_filename.edl” import func_name, func2_name;
Example
	enclave {
		from “secure_comms.edl” import send_email, send_sms;
		from "../../sys/other_secure_comms.edl" import *;
	};

授权访问Ecalls函数

默认行为是:Ecalls 函数不可被任何不可信函数调用
为了使Ecalls 可以直接作为root Ecall 调用的被应用程序调用,这个Ecalls 函数需要使用关键字:public 来修饰。如果没有使用public那么Ecalls 无法作为根Ecalls 直接的调用。
注意:一个enclave中必须有一个public Ecalls 函数,否则无法启动调用。sgx_edger8r会返回一个错误。

  • 为了保证Ocalls 函数可以调用一个ECall 函数,必须通过allow 关键字来指定。public或者private的 Ecalls 都可以使用allow 关键字。
    Syntax
untrusted {
<function prototype> allow (func_name, func2_name,);
};

Example

enclave {
	trusted {
		public void clear_secret();
		public void get_secret([out] secret_t* secret);
		void set_secret([in] secret_t* secret);
	};
untrusted {
	void replace_secret([in] secret_t* new_secret,[out] secret_t* old_secret) allow (set_secret, clear_secret);
	};
};//  在上面的示例中,不受信任的代码被授予对eCalls的不同访问权限。
  • 在上面的示例中,不受信任的代码被授予对eCalls的不同访问权限。

Enclave Configuration File

Enclave 配置文件是一个XML文件,包含了一个Encalve 的用户定义参数。
此XML文件是Enclave项目的一部分。名为SGX_Signe的工具使用此文件作为输入,为Enclave创建签名和元数据。
下面是配置文件的示例

<EnclaveConfiguration>
<ProdID>100ProdID>
<ISVSVN>1ISVSVN>
<StackMaxSize>0x50000StackMaxSize>
<StackMinSize>0x2000StackMinSize>
<HeapMaxSize>0x100000HeapMaxSize>
<HeapMinSize>0x40000HeapMinSize>
<HeapInitSize>0x80000HeapInitSize>
<TCSNum>3TCSNum>
<TCSMaxNum>4TCSMaxNum>
<TCSMinPool>2TCSMinPool>
<TCSPolicy>1TCSPolicy>
<DisableDebug>0DisableDebug>
<MiscSelect>0MiscSelect>
<MiscMask>0xFFFFFFFFMiscMask>
EnclaveConfiguration>

暂时了解即可,不需要了解参数含义。

Enclave 项目配置

依赖于开发环境,选择一个项目配置去构建一个Enclave.
-Simulation
在模拟模式下,可以使用debug或release编译器设置构建enclave。但是,在这两种情况下,都会以enclave调试模式启动enclave。单步签名是签署模拟Enclave的默认方法。
总结

共有四种构建模式:

  • simulation +debug/release 启动模式是Debug (simulation)
  • HardWare +Debug 启动模式是Debug(Debug)
    set SGX_MODE=HW and SGX_DEBUG=1 as parameters to the Makefile during the build.
  • SGX_MODE=HW and SGX_PRERELEASE=1启动模式是Debug (prerelease)
  • HW+release 版本,正式发布版本(Release)
    特殊情况:Two-step method is the default signing method for the Release
    configuration. The enclave needs to be signed with a white-listed key

加载Enclave 或者卸载Enclave

Enclave源码构建称为一个共享对象,为了使用一个Enclave,enclave.so文件需要被加载到受到保护的内存中,通过调用Enclave API sgx_ create_enclave() or sgx_create_encalve_ex()。enclave.so 需要被sgx_sign签名。当首次加载一个Enclave 时, **the loader gets a launch token and saves it back to the in/out parameter token(加载器获取启动令牌并将其保存回in/out参数令牌)**用户可以将启动令牌保存到文件中,以便在第二次加载Enclave时,应用程序可以获得保存在文件中的启动令牌。
提供有效的启动令牌可以提高加载性能。
要卸载一个Enclave ,用户必须调用sgx_destroy_enclave()接口,传递参数sgx_enclave_id_t;

The sample code to load and unload an Enclave is shown below.

#include 
#include 
#include "sgx_urts.h"

#define ENCLAVE_FILE _T("Enclave.signed.so")

int main(int argc, char* argv[])
{
	sgx_enclave_id_t eid;
	sgx_status_t ret = SGX_SUCCESS;
	sgx_launch_token_t token = {0};
	int updated = 0;
	// Create the Enclave with above launch token.
	ret = sgx_create_enclave(ENCLAVE_FILE, SGX_DEBUG_FLAG, &token, &updated, &eid, NULL);
	 //加载一个Enclave ,返回状态ret
	if (ret != SGX_SUCCESS) {
	printf("App: error %#x, failed to create enclave.\n", ret);
	return -1;
	
}
	// A bunch of Enclave calls (ECALL) will happen here.


	//销毁Enclave,根据eid 的值。
	if(SGX_SUCCESS != sgx_destroy_enclave(eid))
	return -1;
	return 0;
}

处理电源事件

被保护的内存的加密密钥是被存储在启动intel sgx的 cup中,(秘钥存于 cup 中,但是cup是启动sgx的那个cup ),每一次发生电源事件(cup停止或者休眠)都会使的秘钥损毁。所以,当发生电源改变(cpu休眠或者暂停)时,Enclave内存将被删除,此后将无法访问所有Enclave数据。当系统恢复时,任何后续的ECall都将失败,返回错误代码
SGX_ERROR_ENCLAVE_LOST,此特定错误代码表明由于电源事件而丢失Enclave。

  • 当在受保护内存中加载一个Enclave时,一个Intel sgx 应用程序应该有能力处理此时由于电源事件出现的任何错误。
  • 为了可以使用最小的冲突,来处理电源事件并且恢复Enclave运行,应用必须在Encalls失败时,有处理SGX_ERROR_ENCLAVE_LOST的能力。
  • 当发生这种错误时, 只有也仅有一个来自应用线程必须销毁Enclave ,sgx_destroy_enclave()并且重新加载它,sgx_create_enclave().**In addition, to resume execution from where it was when the enclave was destroyed, the application should periodically seal and save enclave state information on the platform and use this information to restore the enclave to its original state after the enclave is reloaded.**SDK中包含的Power Transition示例代码演示了此过程。

问题补充

  1. 什么是边缘例程(edge-routines)?
    答:边缘例程又称为:可信桥和可信代理(trusted bridge and trusted proxyEnclave开发基础_第3张图片

Trusted Bridge 和 Trusted Proxy
一个抽象概念. 由于 enclave 和外部环境是完全隔离的, 因此他们中间的参数传递(尤其是指针)必须经过验证, 这一机制被称为 Trusted Bridge 和 Trusted Proxy. 代码实现上, 有 sgx_edger8r 产生的代码的功能就是 Trusted Bridge 和 Trusted Proxy.
有区别吗? 简单的说, Bridge 检测 ECall 的参数, proxy 检测 OCall 的参数.
参考文章

  1. https://blog.lao-yuan.com/2019/02/20/Linux-SGX-Demo.html#using-edl
  2. http://sec-lbx.tk/2016/09/18/SGX/
  1. C++ 如何自定义数据类型

定义自己的数据类型 (typedef)
C++ 允许我们在现有数据类型的基础上定义我们自己的数据类型。我们将用关键字typedef来实现这种定义,它的形式是:
typedef existing_type new_type_name;
这里 existing_type 是C++ 基本数据类型或其它已经被定义了的数据类型,new_type_name 是我们将要定义的新数据类型的名称。例如:

typedef char C;
typedef unsigned int WORD;
typedef char * string_t;
typedef char field [50];

在上面的例子中,我们定义了四种新的数据类型: C, WORD, string_t 和 field ,它们分别代替 char, unsigned int, char* 和 char[50] 。这样,我们就可以安全的使用以下代码:

C achar, anotherchar, *ptchar1;
WORD myword;
string_t ptchar2;
field name;

如果在一个程序中我们反复使用一种数据类型,而在以后的版本中我们有可能改变该数据类型的情况下,typedef 就很有用了。或者如果一种数据类型的名称太长,你想用一个比较短的名字来代替,也可以是用typedef。

什么是宏定义(宏替换)

为什么:合理的使用宏定义和条件编译,可以大大的优化程序、提高程序的质量、增加程序的可移植性。

#define命令是C语言中的一个宏定义命令,它用来将一个标识符(宏名)定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本
C语言中用到宏定义的地方很多,如在头文件中为了防止头文件被重复包含

#ifndef cTest_Header_h
#define cTest_Header_h
//头文件内容
#endif

什么条件编译

核心思想就是 将if else的使用方法 应于到预编译中来使用,ifdef ...else endif

C++中const 的使用

const修饰指针变量时:
(1)只有一个const,如果const位于左侧,表示指针所指数据是常量,不能通过解引用修改该数据;指针本身是变量,可以指向其他的内存单元。 const int * a4 = &a1; ///const data,non-const pointer
(2)只有一个const,如果const位于
右侧,表示指针本身是常量,不能指向其他内存地址;指针所指的数据可以通过解引用修改。 **int * const a5 = &a1; ///non-const data,const pointer**
(3)两个const,*左右各一个,表示指针和指针所指数据都不能修改。

2.const修饰函数参数
传递过来的参数在函数内不可以改变,与上面修饰变量时的性质一样。

    void testModifyConst(const int _x) {
             _x=5;   ///编译出错
             }

参考内容:Intel SGX系列(三)飞地开发基础(二)

你可能感兴趣的:(intel,sgx,Intel,sgx,开发基础)