目录

__create_enclave

进入CLoader::load_enclave

一步步地构建Enclave

建立SECS

回到CLoader::build_image,对ElfParser保存的Enclave文件映像打补丁

建立Section

回到CLoader::build_image,开始建立堆和线程上下文

回到CLoader::build_image,大部分工作完成了,开始初始化Enclave

一步步构建Enclave的最后一步也完成了

完成__create_enclave的后续步骤


执行完各种检查后,走完了_create_enclave_from_buffer_ex函数,会最终进入__create_enclave函数。

从这里开始就开始真正的Enclave建立了。这是一个非常复杂的过程,将会有大量的操作是和SGX驱动一起完成,因为Enclave生命周期管理的硬件指令都是Ring0权限的指令(ENCLS),只能由不可信的驱动来完成。由于驱动不可信,Enclave建立的可信程度由硬件指令自身来保证,此外,还可以通过还可以远程认证来确保远端的Enclave是可信地建立的,这也是由硬件指令来确保的。

__create_enclave

static int __create_enclave(BinParser &parser, uint8_t* base_addr, const metadata_t *metadata, se_file_t& file, const bool debug, SGXLaunchToken *lc, le_prd_css_file_t *prd_css_file, sgx_enclave_id_t *enclave_id, sgx_misc_attribute_t *misc_attr,const uint32_t ex_features,const void* ex_features_p[32])

通过之前Enclave文件内存映射的基址base_addr和ELF解析器parser,生成一个Enclave加载器CLoader。

如果开启了KSS,从扩展特性数组中(ex_feature_p,还记得这个吗?最开头有提到,是APP不可信部分设置的,目前的扩展特性有PCL、KSS、Swtichless)中提取出KSS配置。KSS接下来会用于Enclave加载。启用了KSS却使用不合理的KSS配置,会导致接下来的Enclave加载出现问题。

加载Enclave(调用CLoader::load_enclave_ex),加载的参数包括了KSS配置

//位于psw/urts/loader.cpp
int CLoader::load_enclave_ex(SGXLaunchToken *lc, bool debug, const metadata_t *metadata, sgx_config_id_t *config_id, sgx_config_svn_t config_svn, le_prd_css_file_t *prd_css_file, sgx_misc_attribute_t *misc_attr)

CLoader::load_enclave_ex会调用CLoader::load_enclave,区别是load_enclave_ex会不断地尝试调用load_enclave,因为加载Enclave时可能出现电力切换、内存映射冲突(这种情况只给三次出错机会)。【我目前的理解,因为Enclave.so文件是个动态库,会被映射到虚拟地址中,当发生缺页中断时,会从硬盘调度到物理内存,但前面已经有一个Enclave文件的映射了,这之间什么关系,当时笔记没记,我有点忘了,后面补充。mmap我目前的理解是将文件等对象(能申请到fd句柄的)映射到虚拟内存中,比如让我们能够像访问内存一样,访问硬盘上的文件。留疑问?】

//位于psw/urts/loader.cpp
int CLoader::load_enclave(SGXLaunchToken *lc, int debug, const metadata_t *metadata, sgx_config_id_t *config_id, sgx_config_svn_t config_svn, le_prd_css_file_t *prd_css_file, sgx_misc_attribute_t *misc_attr)

进入CLoader::load_enclave

首先将元数据保存成CLoader的类成员变量m_metadata,并对元数据进行验证(validate_metadata),元数据请参考《SGX中的X特性、SGX获取元数据》。

m_metadata检查项 说明
版本匹配情况 检查元数据版本是不是和当前uRTS匹配,不匹配拒绝加载,因为可能出现各种问题
TCS Policy TCS策略
SSA帧大小  
Enclave大小 检查Enclave大小是否过大,是否是2的幂次
数据目录dirs 检查数据目录dirs中记录的数据是否完好的落在元数据的data数组中。数据目录里面记录的数据分为layout、patch两种。
layout

检查layout表是否合理(调用CLoader::validate_layout_table):统计layout表中的layout项、layout组的(相对于Enclave内部的相对虚拟地址RVA的)偏移Offset以及大小Size,最后根据Enclave内偏移和大小,判断这个layout是否会跨出Enclave的末端(超出Enclave的Size)。
Layout里面记录的内容包括:堆布局、

线程上下文所用到的内存的布局

// thread context memory layout
// guard page | stack | guard page | TCS | SSA | guard page | TLS
// TCS预置为TCS模板(CMetadata::build_tcs_template)

、通用线程上下文布局、tcs_min_pool数量的线程上下文布局、tcs_eremove数量的线程上下文布局、动态线程上下文、RSRV(reserve)区域。

Layout来源于SignTool的CMetadata::build_metadata中对的build_layout_table,它会对Enclave布局信息进行提取保存至元数据的layout。

patch

检查patch表(调用validate_patch_table)判断是否会跨出Enclave的末端(超出Enclave的Size)。

patch这个数据结构,主要比如为了将g_global_data这个全局变量复写到Enclave执行副本中ELRANGE。使得g_global_data里面实际存储的值是有效值,比如Enclave大小。g_global_data中的有效值(或者说这个patch),来源于SignTool的CMetadata::build_metadata中的build_patch_table,它会对Enclave这些信息进行提取保存至patch。

获取杂项和属性(调用get_misc_attr),用于下一步中镜像建立(调用CLoader::build_image)

ret = build_image(lc, &sgx_misc_attr.secs_attr, config_id, config_svn, prd_css_file, &sgx_misc_attr);

一步步地构建Enclave

调用CLoader::build_image

int CLoader::build_image(SGXLaunchToken * const lc, sgx_attributes_t * const secs_attr, sgx_config_id_t *config_id, sgx_config_svn_t config_svn, le_prd_css_file_t *prd_css_file, sgx_misc_attribute_t * const misc_attr)

建立镜像,会分很多步。uRTS(也就是我们CLoader当前所处的运行时环境,当前还在初始化Enclave,并不是在Enclave中)需要通过SGX驱动构建Enclave。具体来说uRTS加载器通过ioctl与驱动通信,每个通信指令(信令)对应一个驱动的不同功能的函数句柄,函数句柄通过宏_encls及其对应的汇编代码来调用硬件指令(opcode:0x0f01cf)

首先建立SECS(调用build_secs),每一个SECS象征着一个Enclave。每个SECS只有对应的Enclave能够访问,其他Enclave不行。

建立SECS

调用CLoader::build_secs。

总的来说。uRTS中的加载器先构建SECS副本,然后通过驱动调用ECREATE指令来初始化SECS到EPC中。具体来说,ECREATE会指定Enclave的基址和大小(地址空间属于APP的线性空间的一部分,但是具体的物理Page是来自于EPC,EPC受到EPCM、段、页访问控制限制),然后为SECS副本专门分配一个EPC页作为一个SECS页(SECS不需要出现在APP的线性空间中,而且SECS过于敏感,只能由CPU访问)。建立SECS完成后我们会得到一个EnclaveID。

int CLoader::build_secs(sgx_attributes_t * const secs_attr, sgx_config_id_t *config_id, sgx_config_svn_t config_svn, sgx_misc_attribute_t * const misc_attr)
/*SECS data structure*/
typedef struct _secs_t
{uint64_t                    size;           /* (  0) Size of the enclave in bytes */PADDED_POINTER(void,        base);          /* (  8) Base address of enclave */uint32_t                    ssa_frame_size; /* ( 16) size of 1 SSA frame in pages */sgx_misc_select_t           misc_select;    /* ( 20) Which fields defined in SSA.MISC */
#define SECS_RESERVED1_LENGTH 24uint8_t                     reserved1[SECS_RESERVED1_LENGTH];  /* ( 24) reserved */sgx_attributes_t            attributes;     /* ( 48) ATTRIBUTES Flags Field */sgx_measurement_t           mr_enclave;     /* ( 64) Integrity Reg 0 - Enclave measurement */
#define SECS_RESERVED2_LENGTH 32uint8_t                     reserved2[SECS_RESERVED2_LENGTH];  /* ( 96) reserved */sgx_measurement_t           mr_signer;      /* (128) Integrity Reg 1 - Enclave signing key */
#define SECS_RESERVED3_LENGTH 32uint8_t                     reserved3[SECS_RESERVED3_LENGTH];  /* (160) reserved */sgx_config_id_t             config_id;      /* (192) CONFIGID */sgx_prod_id_t               isv_prod_id;    /* (256) product ID of enclave */sgx_isv_svn_t               isv_svn;        /* (258) Security Version of the Enclave */sgx_config_svn_t            config_svn;     /* (260) CONFIGSVN */
#define SECS_RESERVED4_LENGTH 3834uint8_t                     reserved4[SECS_RESERVED4_LENGTH];/* (262) reserved */
} secs_t;

首先对CLoader的成员变量m_secs简单的初始化一下。结构体如上。m_secs初始化如下表。

m_secs成员变量 说明
m_secs.base Enclave的基址 初始化为0,后面会由SGX驱动来选择
m_secs.size Enclave虚拟大小 m_metadata->enclave_size,可以看到值取自元数据
m_secs.misc_select 杂项选择情况(注意是杂项,比如EXITINFO,不是属性Attribute) misc_attr->misc_select,前面调用get_misc_attr获取的杂项选择
m_secs.attributes 属性(也叫做SECS属性,表明X特性情况) secs_attr,前面调用get_misc_attr获取的属性Attribute
m_secs.ssa_frame_size SSA帧大小 m_metadata->ssa_frame_size,也就是说这个值来自元数据中的记录
m_secs.config_id

配置ID(这个配置ID和配置SVN是在新特性KSS中出现,所以老版本的SGX手册图上这块区域还属于保留区域))

config_id,这个值最根本来自于我们在调用

sgx_create_enclave_ex时传入的kss配置参数,这个只有在开启了KSS扩展的情况下才会赋予有意义的值(非NULL)。

m_secs.config_svn 配置SVN(安全版本号,用于标识SGX中的软硬件组件的最新程度,防止旧版本的SVN组件读取新版本SVN组件加密等操作的数据) config_svn,这个值也是源自于我们在调用

sgx_create_enclave_ex时传入的kss配置参数,这个只有在开启了KSS扩展的情况下才会赋予有意义的值(非0)。

获取Enclave创建器(这里是硬件模式),用于创建Enclave(前面出现过Enclave创建器,但是只是用来获取各种信息,比如杂项和属性),这里实际上是创建Enclave的SECS结构体,并与之对应的空的Enclave。调用(EnclaveCreatorHW::create_enclave)如下

int ret = enclave_creator->create_enclave(&m_secs, &m_enclave_id, &m_start_addr, is_ae(&m_metadata->enclave_css));
int EnclaveCreatorHW::create_enclave(secs_t *secs, sgx_enclave_id_t *enclave_id, void **start_addr, bool ae)

先看一眼参数。前面我们有过一个base_addr,那个地址是Enclave文件映射到的地址(用于Enclave信息解析,及后续Enclave文件内容搬到真正的Enclave中去)。这里的m_start_addr目前为NULL,未来会成为Enclave的起始地址,enclave_id也将被赋值。secs也是如此,它来自m_secs,是一个简单初始化过的对象。而ae参数不再使用。

【注意一下,这是一个common的接口,已经在(psw/enclave_common/sgx_enclave_common.lds)中标记为一个全局的符号。Common代表这个接口可以直接调用,在SampleCommonLoader这个例子中,就是直接用这个API进行Enclave创建。并且SampleCommonLoader本身就是为了阐述Enclave Common Loader Layer API的用法,这里的enclave_create参数如下】

//sgx_create_enclave_ex中层层调用后的使用enclave_create的形式
void* enclave_base = enclave_create(NULL, (size_t)secs->size, 0, ENCLAVE_TYPE_SGX2, &enclave_create_sgx, sizeof(enclave_create_sgx_t), &enclave_error);//直接作用Common接口调用
void *enclave_base = enclave_create(NULL, (size_t)secs->size, sgx_enclave_blob_length, ENCLAVE_TYPE_SGX1, &create_ioc, sizeof(create_ioc), &enclave_error);

【enclave_create就是一个用于创建Enclave SECS的Common API,如果想要完整的创建Enclave,比如把Enclave文件内容搬到Enclave中,我们还需要enclave_load_data、enclave_initialize、enclave_delete等Common接口。sgx_create_enclave{_ex}是一个傻瓜式的一件创建Enclave的函数,他包含了并用到所有Common API的功能,并且还支持Enclave文件映射到内存,方便Enclave文件转入到Enclave中,如果只用Common接口,很多东西都会变得非常繁琐。】

【sgx_create_enclave{_ex}和enclave_create的区别,可以看《再回顾SGX初始化(一)——环境检查》最开始的介绍。】

/* enclave_create()* Parameters:*      base_address [in, optional] - An optional preferred base address for the enclave.*      virtual_size [in] - The virtual address range of the enclave in bytes.*      initial_commit[in] - The amount of physical memory to reserve for the initial load of the enclave in bytes.*      type [in] - The architecture type of the enclave that you want to create.*      info [in] - A pointer to the architecture-specific information to use to create the enclave.*      info_size [in] - The length of the structure that the info parameter points to, in bytes.*      enclave_error [out, optional] - An optional pointer to a variable that receives an enclave error code.* Return Value:*      If the function succeeds, the return value is the base address of the created enclave.*      If the function fails, the return value is NULL. The extended error information will be in the enclave_error parameter if used.
*/
extern "C" void* COMM_API enclave_create(COMM_IN_OPT void* base_address,COMM_IN size_t virtual_size,COMM_IN size_t initial_commit,COMM_IN uint32_t type,COMM_IN const void* info,COMM_IN size_t info_size,COMM_OUT_OPT uint32_t* enclave_error)

那我们接下来看看这个函数是怎么创建SECS的吧~

首先检查type、info这两个参数,info是如下的结构体,里面只用来记录SECS信息(副本,SECS最终要存在EPC中,只能由CPU访问,因为这个数据结构代表了Enclave,非常敏感重要)。

typedef struct enclave_create_sgx_t {uint8_t secs[SECS_SIZE];
} enclave_create_sgx_t;

然后打开SGX驱动。

其实我们之前打开过SGX驱动了,我写在了《SGX中的X特性、SGX获取元数据》中“检查是否支持SGX v2”,也就是说之前获取元数据过程中,我们已经试图打开过SGX驱动,那时候也是第一次通过和SGX驱动通信来确定驱动是不是支持EDMM。当时我们只对out-of-tree类型的SGX驱动进行打开并用于判断EDMM是否支持,并且打开后的SGX驱动句柄是作为EnclaveCreatorHW的类成员变量。

而此处需要重新打开SGX驱动。我们对out-of-tree、dcap、in kernel三种类型的其中一种的SGX驱动进行启动。in kernel模式的SGX驱动目前的代码没有对基址和大小进行内存对齐(这个操作取决于用户模式),因此先将Enclave大小设置为Enclave虚拟大小的两倍,其中未使用的区域将在稍后通过调用munmap来释放。【我目前以传统的SGX驱动(也就是out-of-tree驱动,绑定到/dev/isgx)进行讲解。】

拿到了SGX驱动句柄后,将SGX驱动绑定的设备(如/dev/isgx)mmap映射到base_address偏好的位置处,但实际上这个基址经常采用NULL,让内核自己去决定具体啥位置(如果是in kernel模式的SGX驱动,那么采用匿名映射)。得到映射地址enclave_base。(如果是in kernel类型的SGX驱动,那么对映射地址enclave_base(按照虚拟大小virtual_size的整数倍)进行内存对齐,对齐的方法可以参考这里,其中有一个前提是)

将m_secs.base设置为这个SGX驱动绑定设备的映射地址enclave_base(这个值初始化的时候是个0)。紧接着就要通过ioctl与SGX驱动通信(信令是SGX_IOC_ENCLAVE_CREATE)。驱动接收到这个信令后,就会检验这个SECS,为这个SECS分配一个EPC页来专门存它,之后会通过ECREATE硬件指令完成SECS的建立以及一个空的Enclave的建立,所建立的SECS将只能由CPU进行访问。

in kernel和dcap(需要检查是否开启了一个是否能够从EGETKEY硬件指令获取配置密钥的属性选项)两种SGX驱动支持特殊的配置密钥访问模式。

dcap支持配置密钥访问的白名单机制,具体来说还与/dev/sgx_prv打交道(SGX_IOC_ENCLAVE_SET_ATTRIBUTE)。in kernel还与/dev/sgx/provision设备打交道(SGX_IOC_ENCLAVE_SET_ATTRIBUTE_IN_KERNEL)。

之后在临界区对如下表所示的全局变量进行设置(要记得此时我们Enclave的SECS已经建立完成了)。

全局变量,map数据结构 说明 保存的内容
s_hfile[enclave_base] 设备文件句柄 hdevice_temp(指向/dev/sgx/provision设备句柄,只在InKernel的SGX驱动下需要这样做,后续会将设备句柄映射到ELRANGE中)
s_enclave_size[enclave_base] enclave大小 virtual_size,值就是SECS中的Enclave虚拟大小
s_secs_attr[enclave_base] SECS中的属性 secs_attr,这个值就是SECS中存放的属性
s_enclave_mem_region[enclave_base]

enclave内存区域。

这个变量代表了当前Enclave中尚未构建完毕的内存区域(虚拟内存区域就是说连续的具有相同权限的虚拟区域,比如同一个Section内【Segment】)。

我们每次要构建新的内存区域的时候,要对上一个内存区域的构建进行收尾,主要是上一个内存区域通过mprotect设定好权限

.addr、.len、.prot值为0。

紧接着enclave_create就返回到EnclaveCreatorHW::create_enclave,返回了enclave_base值。设置m_secs.base、m_start_addr、m_enclave_id。(如下表所示)

再紧接着,就回到CLoader::build_secs,代表SECS创建完,然后首次对m_secs.mr_enclave赋值(如下表)。到此CLoader::build_secs就都执行完了。

CLoader成员变量 说明
m_start_addr 赋值为SGX驱动绑定的设备(如/dev/isgx)映射基址,也就是enclave_create返回的enclave_base值
m_enclave_id 最最开始它的值初始化为0,这里进行赋值——一个全局变量原子性的加一。
m_secs成员变量 说明
m_secs.base Enclave的基址 初始化为0,后面会赋值为SGX驱动绑定的设备映射基址,也就是enclave_create返回的enclave_base值
m_secs.size Enclave虚拟大小 m_metadata->enclave_size,可以看到值取自元数据
m_secs.misc_select 杂项选择情况(注意是杂项,比如EXITINFO,不是属性Attribute) misc_attr->misc_select,前面调用get_misc_attr获取的杂项选择
m_secs.attributes 属性(也叫做SECS属性,表明X特性情况) secs_attr,前面调用get_misc_attr获取的属性Attribute
m_secs.ssa_frame_size SSA帧大小 m_metadata->ssa_frame_size,也就是说这个值来自元数据中的记录
m_secs.config_id

配置ID(这个配置ID和配置SVN是在新特性KSS中出现,所以老版本的SGX手册图上这块区域还属于保留区域))

config_id,这个值最根本来自于我们在调用

sgx_create_enclave_ex时传入的kss配置参数,这个只有在开启了KSS扩展的情况下才会赋予有意义的值(非NULL)。

m_secs.config_svn 配置SVN(安全版本号,用于标识SGX中的软硬件组件的最新程度,防止旧版本的SVN组件读取新版本SVN组件加密等操作的数据) config_svn,这个值也是源自于我们在调用

sgx_create_enclave_ex时传入的kss配置参数,这个只有在开启了KSS扩展的情况下才会赋予有意义的值(非0)。

m_secs.mr_enclave Enclave哈希 m_metadata->enclave_css.body.enclave_hash,这个值来自于Enclave文件元数据中的签名结构体的Enclave哈希信息,这个哈希值代表了Enclave可信建立的正确度量值。

回到CLoader::build_image,对ElfParser保存的Enclave文件映像打补丁

先读取reloc bitmap,后续建立Section会用到,之后我们才进行打补丁,防止reloc bitmap读取之前被覆盖。

打补丁(patch数据结构)的目的,主要比如为了将g_global_data这个全局变量复写到Enclave执行副本中ELRANGE。使得g_global_data里面实际存储的值是有效值,比如Enclave大小。这个g_global_data中的有效值(或者说这个patch),来源于SignTool中

bool CMetadata::build_metadata(const xml_parameter_t *parameter){...build_patch_table...
}

会对Enclave这些信息进行提取保存至patch。

(我们前面提到过对Patch表、Layout表进行验证是否越界。Layout表的内容后续会加载)

#define LAYOUT_ENTRY_NUM 42
typedef struct _global_data_t
{sys_word_t     sdk_version;sys_word_t     enclave_size;sys_word_t     heap_offset;sys_word_t     heap_size;sys_word_t     rsrv_offset;sys_word_t     rsrv_size;sys_word_t     rsrv_executable;sys_word_t     thread_policy;sys_word_t     tcs_max_num;thread_data_t  td_template;uint8_t        tcs_template[TCS_TEMPLATE_SIZE];uint32_t       layout_entry_num;uint32_t       reserved;layout_t       layout_table[LAYOUT_ENTRY_NUM];
} global_data_t;

建立Section

调用CLoader::build_sections

总的来说。uRTS会通过SGX驱动调用EADD指令来添加页,EADD硬件指令会初始化对应的EPCM项,然后将EPC页分配给Enclave,并把不可信内存中的Enclave文件映射代码等以每次4KB页大小拷贝到EPC页上(第一次分析的那个SGX驱动版本下,不支持其他页大小)。每当一个EPC页被添加,可以通过调用EEXTEND指令来对该页以每次256字节的验证,由于页大小4K,所以一个页要验证16次。值得注意的是,每次添加或者验证EPC页,都会将记录保存到SECE的加密日志中去。

《Enclave虚拟内存视图》

int CLoader::build_sections(std::vector<uint8_t> *bitmap)

遍历之前ElfParser从Enclave文件映射中提取的所有Section,对每个Section进行内存区域建立(调用build_mem_region),所依赖的信息如下。

Section类成员变量(先前ELF文件解析时提取) 说明
m_start_addr Section在Enclave文件映射中的起始位置,调用sections[i]->raw_data()
m_raw_data_size Section在Enclave文件映射中的大小,调用sections[i]->raw_data_size()
m_rva Section建立起来以后,它所在的相对Enclave基址的虚拟地址,调用sections[i]->get_rva()
m_virtual_size Section建立起来以后,所占用的虚拟地址空间大学,调用sections[i]->virtual_size()
m_si_flags

Section的页权限,之前ELF解析的时候,我们指定Section的页权限首先是一个Regular页,然后在此基础上根据Segment(个人理解当时ELF解析的时候是按照Segment粒度来设置Section类的)的时候情况设置RWX权限。调用sections[i]->get_si_flags()

bitmap(这个非Section类成员变量) 就是前一步中的重定位bitmap,因为我们需要考虑到实际加载过程中,有的页保存的是一些重定位数据,这些页需要标记为可写。

那么如何对每个Section(实际上我的觉得应该是Segment,比如数据段、代码段等等)进行建立呢(build_mem_region做了什么)?由于有的页包含了重定位数据,我们需要一个页一个页的加载,并且每加载一个页之前,对于重定位的并且本来不具备写权限的页,我们将通过mprotect对这个虚拟地址页赋予写权限,注意我们操作的页的虚拟地址是

//操作的页地址       rva+m_start_addr
mprotect((void*)(TRIM_TO_PAGE(rva) + (uint64_t)m_start_addr), SE_PAGE_SIZE, (int)(sinfo.flags & SI_MASK_MEM_ATTRIBUTE));
//rva              这个页相对于Enclave基址的偏移
//m_start_addr     Enclave的基址,也就是SGX驱动绑定设备如/dev/isgx的映射空间

然后对Section内的所有页进行建立(build_pages),也就是说把Enclave文件映射中这个Section的内容一页一页的搬到Enclave中的指定RVA上去(同时也会有对应的物理EPC被使用)。这里会用到Enclave创建器来调用函数来加载Enclave页,具体的过程需要通过SGX驱动来实施。首先检查一下

检查项 说明
RVA 检查相对Enclave虚拟地址基址的偏移是否页对齐
attr 本例中是ADD_EXTEND_PAGE,包含了PAGE_ATTR_EADD和PAGE_ATTR_EEXTEND,我们这里检查它是否包含PAGE_ATTR_EEXTEND(1<<DoEEXTEND),如果没有EXTEND度量,那么我们就需要在原来(标准页+读写执行)权限基础上添加这个Enclave页未度量的标记(ENCLAVE_PAGE_UNVALIDATED),这说的页权限会作为下文中data_properties参数

二话不说,直接进入enclave_load_data(这是个Common API,我们前面提到过,SampleCommonLoader)

/* enclave_load_data()* Parameters:*      target_address [in] - The address in the enclave where you want to load the data.*      target_size [in] - The size of the range that you want to load in the enclave, in bytes. *      source_buffer [in, optional] - An optional pointer to the data you want to load into the enclave.*      data_properties [in] - The properties of the pages you want to add to the enclave.*      enclave_error [out, optional] - An optional pointer to a variable that receives an enclave error code.* Return Value:*      The return value is the number of bytes that was loaded into the enclave.*      If the number is different than target_size parameter an error occurred. The extended error information will be in the enclave_error parameter if used.
*/
extern "C" size_t COMM_API enclave_load_data(COMM_IN void* target_address,COMM_IN size_t target_size,COMM_IN_OPT const void* source_buffer,COMM_IN uint32_t data_properties,COMM_OUT_OPT uint32_t* enclave_error)

刚进来先对目标地址及其大小、数据属性检查一下

检查项 说明
target_address 不能为NULL,且必须要页对齐
target_size 必须大于等于4K,实际上我们这里是以4K大小一页一页的加载。并且这个数值必须得是4K倍数

data_properties

如果这个页是TCS(用来管理线程的一个重要的结构体),那么需要清除这个页的RWX位

根据驱动的不同,添加页的流程也有些区别,这里还是以Out-of-tree为主线,做法是把参数

参数打包项 说明
目标地址 RVA+m_start_addr
源代码地址 就是Enclave文件映射中对应Section的对应页
Section属性  

通过ioctl使用SGX_IOC_ENCLAVE_ADD_PAGE信令让SGX驱动添加页(也就是前面说的加载页的意思,个人觉得“加载”可能更形象一些,添加页也就是将代码页一页一页地添加到空的Enclave中去),并且在常规情况下会对Enclave页进行度量,未来在EINIT硬件指令时需要和MRENCLAVE比对判断是否正确的可信启动。DCAP SGX驱动做法同Out-of-tree。InKernel SGX驱动也有些接近,使用SGX_IOC_ENCLAVE_ADD_PAGES_IN_KERNEL信令。

这里要引入一个虚拟内存区域的概念,这个概念就是说连续的具有相同权限的虚拟区域,比如一整个Section【Segment】。

接近着,将上一块内存区域利用mprotect设置内存属性并完成上一块内存区域的全部构建。对新的内存区域进行构建,并对s_enclave_mem_region[enclave_base]的值进行更新,当前内存区域最终设置(主要是最后的mprotect进行内存属性设置)会留到下一轮内存区域设置的时候,或者Enclave后续初始化的时候完成,主要还是因为还在设置当前Enclave内存区域的时候不知道当前加载的页是不是最后一块。

其中,enclave_base是根据目标地址来推导的(调用get_enclave_base_address_from_address,其实就是判断目标地址落在哪一个enclave_base中,逻辑挺简单)。

InKernel SGX驱动需要把/dev/sgx/provision设备句柄映射到ELRANGE中的当前内存区域。

全局变量,map数据结构 说明 保存的内容
s_hfile[enclave_base] 设备文件句柄 hdevice_temp(指向/dev/sgx/provision设备句柄,只在InKernel的SGX驱动下需要这样做,后续会将设备句柄映射到ELRANGE中)
s_enclave_size[enclave_base] enclave大小 virtual_size,值就是SECS中的Enclave虚拟大小
s_secs_attr[enclave_base] SECS中的属性 secs_attr,这个值就是SECS中存放的属性

s_enclave_mem_region[enclave_base]

【更新】

enclave内存区域。

这个变量代表了当前Enclave中尚未构建完毕的内存区域(虚拟内存区域就是说连续的具有相同权限的虚拟区域,比如同一个Section内【Segment】)。

我们每次要构建新的内存区域的时候,要对上一个内存区域的构建进行收尾,主要是上一个内存区域通过mprotect设定好权限

.addr的值更新为当前ELRANGVE中相同属性的一块虚拟内存区域的基址。

比如一整个Section、一整块特殊的内存区域的基址。

.len更新为当前虚拟内存区域的实际长度
.prot更新为当前虚拟内存区域的实际属性

对一个Seciton的建立过程中的主体部分的页建立到此就完成了。之后对row_data_size到virtual_size区间的内存页进行建立,这里主要留给那些未初始化的数据用的,内存值均用0来赋值(通过memset)。

至此,一个Section的建立就完成了(通过调用build_mem_region)。多个Section就是外边套一个循环。SGX1.5版本的Enclave文件(可以从元数据看出来)在建立Section过程中还要在Section尾巴后面添加一个全0页(目的是什么,尚不清楚,其他版本并不需要这个额外的操作)。

回到CLoader::build_image,开始建立堆和线程上下文

调用CLoader::build_contexts。参数是元数据中的layout数据。layout布局数据结构、种类可以参考《SGX中的X特性、SGX获取元数据》

布局是一些特殊的内存页,包括堆布局、线程上下文所用到的内存的布局

// thread context memory layout
// guard page | stack | guard page | TCS | SSA | guard page | TLS
// TCS预置为TCS模板(CMetadata::build_tcs_template)

、通用线程上下文布局、tcs_min_pool数量的线程上下文布局、tcs_eremove数量的线程上下文布局、动态线程上下文、RSRV(reserve)区域。这需要在前几步代码加载完毕后,对这些特殊布局页进行额外加载。

if (SGX_SUCCESS != (ret = build_contexts(GET_PTR(layout_t, m_metadata, m_metadata->dirs[DIR_LAYOUT].offset), GET_PTR(layout_t, m_metadata, m_metadata->dirs[DIR_LAYOUT].offset + m_metadata->dirs[DIR_LAYOUT].size), 0)))
int CLoader::build_contexts(layout_t *layout_start, layout_t *layout_end, uint64_t delta)

之前说过layout分为布局项和布局组。对于每一个布局组,它对应了多个布局项。对于每一个布局项,我们都调用

if(SGX_SUCCESS != (ret = build_context(delta, &layout->entry)))
int CLoader::build_context(const uint64_t start_rva, layout_entry_t *layout)

建立布局大体如下(build_contexts会调用build_context来完成布局项建立)

判断条件 操作
布局属性包含了EADD,说明需要添加到EPC中及对应的Enclave虚拟地址空间 布局内容相对于元数据的偏移不为0 该布局是一个TCS

将TCS的内容加载到TCS布局中记录的Enclave内部的RVA中(同时也会有物理EPC被使用)。调用之前提到过的build_pages。

此对于非EREMOVE属性的TCS,加入到一个记录所有TCS的向量数据结构(m_tcs_list),后续会使用

该布局并不是一个TCS 将布局的内容作为一个内存区域加载到布局中记录的Enclave内的RVA中(同时也会有物理EPC被使用)。调用之前提到过的build_mem_region。
布局内容相对于元数据的偏移为0,并且布局的安全信息标识不为SI_FLAG_NONE 布局的内容大小不为0 将一页大小的源内容充斥为布局内容大小值,然后调用build_pages对该布局建立
布局的内容大小为0 将源内容置NULL,然后调用build_pages对该布局建立。
布局属性包含了POST_ADD,我的理解是这些布局选项如TCS是预先设置好,按需添加到EPC中 布局代表的是动态的TCS 将这个布局加入到一个记录所有TCS的向量数据结构(m_tcs_list),后续会使用

回到CLoader::build_image,大部分工作完成了,开始初始化Enclave

ret = get_enclave_creator()->init_enclave(ENCLAVE_ID_IOCTL, const_cast<enclave_css_t *>(&m_metadata->enclave_css), lc, prd_css_file);

其实前面的工作也是Enclave初始化的一部分,这里所谓的初始化,是指后面会通过EINIT硬件指令确保Enclave是被可信地启动起来了,完成初始化中非常重要的一步。LE有额外的一次尝试初始化Enclave的机会,因为LE可能需要另外从prd_css_file(大概意思就是产品签名结构体文件)中提取签名结构体来进行Enclave初始化。

ret = try_init_enclave(enclave_id, &css, NULL);

linux-sgx v2.10里面之前创建的SGXLaunchToken不再使用,而是在之后重新生成获得,因为DCAP、InKernel类型SGX驱动不再需要LaunchToken。

随后就会进入enclave_initialize这个Common接口。enclave_initialize返回之后,会对SIGSEGV、SIGFPE、SIGILL、SIGBUS、SIGTRAP这五个信号注册一个信号处理句柄(Signal Handler),利用用户上下文信息处理异常,具体如下

来自Enclave内部或ERESUME的异常 通过ecall(ECMD_EXCEPT,...)进入Enclave完成异常处理 之前进入的Enclave处理函数,已成功返回 异常处理成功
enclave内部处理异常过程中返回SGX_ERROR_ENCLAVE_LOST或SGX_ERROR_STACK_OVERRUN 尝试进入Enclave来处理异常过程中发生Enclave丢失、栈溢出
Enclave内部修复不了这个异常 让之前的信号处理句柄来进行处理,如果上一个信号处理句柄是默认的哪个,那么就会终止APP执行
来自EENTER指令的异常 当前信号处理句柄直接返回说明Enclave丢失
不是SE的异常 如果老的信号处理句柄是默认的那个 将这个信号编号对应的信号处理句柄重新设置为默认的,然后使用这个默认的信号处理句柄
如果老的信号处理句柄不是默认的那个 将信号交给老的信号处理句柄处理
enclave_initialize((void*)enclave_id, &enclave_init_sgx, sizeof(enclave_init_sgx), &enclave_error);
/* enclave_initialize()* Parameters:*      base_address [in] - The enclave base address as returned from the enclave_create API.*      info [in] - A pointer to the architecture-specific information to use to initialize the enclave. *      info_size [in] - The length of the structure that the info parameter points to, in bytes.*      enclave_error [out, optional] - An optional pointer to a variable that receives an enclave error code.* Return Value:*      non-zero - The function succeeds.*      zero - The function fails and the extended error information will be in the enclave_error parameter if used.
*/
extern "C" bool COMM_API enclave_initialize(COMM_IN void* base_address,COMM_IN const void* info,COMM_IN size_t info_size,COMM_OUT_OPT uint32_t* enclave_error)

现在着眼于如何初始化Enclave。

检查基地址、信息、信息大小参数。将之前Enclave内部的最后一块内存区域使用mprotect进行属性设置。对于InKernel SGX驱动,需要额外从基地址获取/dev/sgx/provision文件句柄,将文件句柄映射到ELRANGE的最后一块内存区域。(此时我猜测InKernel SGX驱动与Out-of-tree的区别在于InKernel SGX驱动将Enclave的虚拟内存从用户态虚拟地址空间隐藏,只有内核态才能得知Enclave的Symbal符号的虚拟地址?目前纯属猜测)。

Out-of-tree类型SGX驱动 从s_secs_attr取出SECS属性,从先前的传参去除签名结构体 是LE 不需要获取LaunchToken 通过Out-of-tree类型SGX驱动调用EINIT指令,完成Enclave初始化,信令SGX_IOC_ENCLAVE_INIT
不是LE 首先需要从SGX_LAUNCH_SO这个动态库获取符号get_launch_token_func的地址,然后用这个函数获取LaunchToken(需要传参SECS属性和签名结构体,需要从LE申请得到Token)
DCAP类型SGX驱动 不需要获取LaunchToken,只需要Enclave基址和签名结构体 通过DCAP类型SGX驱动完成Enclave初始化,信令SGX_IOC_ENCLAVE_INIT_DCAP
 
InKernel类型SGX驱动 不需要获取LaunchToken以及Enclave基址,只需要签名结构体 通过InKernel类型SGX驱动完成Enclave初始化,信令SGX_IOC_ENCLAVE_INIT_IN_KERNEL

通过SGX驱动调用EINIT硬件指令来完成所有初始化。其中会完成加密日志并建立Enclave标识。具体来说是,如果正常建立Enclave,那么加密过程日志会匹配上Enclave所有者的SIGSTRUCT签名结构体,这同样可以被用于远端认证;EINIT还会验证EINITTOKEN来判断是否Enclave被正确建立并且受平台支持;建立起Enclave标识——Enclave内容、建立顺序、地址、页的安全属性等内容计算出来的哈希值;建立密封标识:确认SIGSTRUCT是与附在SIGSTRUCT中的公钥相匹配、检查Enclave度量值与SIGSTRUCT吻合、检查Enclave属性与SIGSTRUCT中声明的兼容、完成Enclave度量并记录密封标识和Enclave标识到SECS。EINIT硬件指令过程请参考《SGX软件栈(二)——硬件指令》最后的“EINIT指令过程”。

DCAP、InKernel类型SGX驱动的出现也就说明为什么之前获取的LaunchToken为什么不用了,而是当前使用Out-of-tree类型时重新获得。

以上就是完成了Enclave初始化。如果之前已经初始化过Enclave就返回Enclave错误之ENCLAVE_ALREADY_INITIALIZED(通过观察s_enclave_init[base_address]是否为true)。如果Enclave初始化成功,并且先前没有初始化过当前Enclave,那么把s_enclave_init[base_address]设置为True。

全局变量,map数据结构 说明 保存的内容
s_hfile[enclave_base] 设备文件句柄 hdevice_temp(指向/dev/sgx/provision设备句柄,只在InKernel的SGX驱动下需要这样做,后续会将设备句柄映射到ELRANGE中)
s_enclave_size[enclave_base] enclave大小 virtual_size,值就是SECS中的Enclave虚拟大小
s_secs_attr[enclave_base] SECS中的属性 secs_attr,这个值就是SECS中存放的属性

s_enclave_mem_region[enclave_base]

enclave内存区域。

这个变量代表了当前Enclave中尚未构建完毕的内存区域(虚拟内存区域就是说连续的具有相同权限的虚拟区域,比如同一个Section内【Segment】)。

我们每次要构建新的内存区域的时候,要对上一个内存区域的构建进行收尾,主要是上一个内存区域通过mprotect设定好权限

.addr保存当前ELRANGVE中相同属性的一块虚拟内存区域的基址。

比如一整个Section、一整块特殊的内存区域的基址。

.len保存当前虚拟内存区域的实际长度
.prot保存为当前虚拟内存区域的实际属性

s_enclave_init[base_address]

【更新】

当前Enclave是否初始化过

值更新为True,代表当前Enclave已经初始化

到此enclave_initialize这个Common API就完成所有的任务了。

一步步构建Enclave的最后一步也完成了

CLoader::build_image返回

CLoader::load_enclave返回

CLoader::load_enclave_ex返回

前面主要完成了Enclave的建立,主要是Enclave的ECREATE、EADD、EEXTEND、EINIT硬件指令过程及配套的配置过程。

完成__create_enclave的后续步骤

《再回顾SGX初始化(二)》到此结束了,后续内容请见《再回顾SGX初始化(三)》。

后续预告:CEnclave对象会包含当前这个Loader,然后进行KSS初始化、Switchless初始化、为RTS准备好CPUID返回值等。

再回顾SGX初始化(二)——uRTS端构建Enclave相关推荐

  1. SGX初始化中ELF文件解析

    先记 ElfParser::run_parser()函数是SGX初始化<再回顾sgx_create_enclave>慢慢长征路的中间一环.比较独立又有些复杂,单独抽出来讲. ELF文件布局 ...

  2. 花书+吴恩达深度学习(二十)构建模型策略(超参数调试、监督预训练、无监督预训练)

    目录 0. 前言 1. 学习率衰减 2. 调参策略 3. 贪心监督预训练 4. 贪心逐层无监督预训练 如果这篇文章对你有一点小小的帮助,请给个关注,点个赞喔~我会非常开心的~ 花书+吴恩达深度学习(十 ...

  3. 小白入门计算机视觉系列——ReID(二):baseline构建:基于PyTorch的全局特征提取网络(Finetune ResNet50+tricks)

    ReID(二):baseline构建:基于PyTorch的全局特征提取网络(Finetune ResNet50+tricks) 本次带来的是计算机视觉中比较热门的重点的一块,行人重识别(也叫Perso ...

  4. C. DS二叉平衡树构建(教材版)

    [id:157][20分]C. DS二叉平衡树构建 题目描述 在初始为空的平衡二叉树中依次插入n个结点,请输出最终的平衡二叉树. 要求实现平衡二叉树,不可以使用各类库函数. AVL代码参考模板: #i ...

  5. 【Android 逆向】Android 逆向通用工具开发 ( PC 端工程分析 | 网络初始化操作 | PC 端工程核心业务逻辑 )

    文章目录 前言 一.网络初始化操作 二.PC 端工程核心业务逻辑 三.博客资源 前言 本篇博客重点分析 PC 端 hacktool 模块 ; 一.网络初始化操作 HackCommand::Prepar ...

  6. 【Android 逆向】Android 逆向通用工具开发 ( 网络模块开发 | SOCKET 网络套接字初始化 | 读取远程端 “Android 模拟器“ 信息 | 向远程端写出数据 )

    文章目录 前言 一.SOCKET 网络套接字初始化 二.SOCKET 网络套接 读取远程端 ( Android 模拟器 ) 信息 三.SOCKET 网络套接 向远程端 ( Android 模拟器 ) ...

  7. Maven学习总结(二)——Maven项目构建过程练习

    2019独角兽企业重金招聘Python工程师标准>>> Maven学习总结(二)--Maven项目构建过程练习 上一篇只是简单介绍了一下maven入门的一些相关知识,这一篇主要是体验 ...

  8. 【博客项目】—案例初始化(二)

    [博客项目]-案例初始化(二) 构建模块化路由 构建博客管理页面模板 渲染模板

  9. GPS定位系统(二)——Android端

    前言 GPS系列--Android端,github项目地址 tag: gps_mine Android移动端,主要是使用高德地图定位,后台上传定位信息,然后就是想办法尽量保活. 包括两个小功能:1.上 ...

  10. ML之分类预测之ElasticNet:利用ElasticNet回归对二分类数据集构建二分类器(DIY交叉验证+分类的两种度量PK)

    ML之分类预测之ElasticNet:利用ElasticNet回归对二分类数据集构建二分类器(DIY交叉验证+分类的两种度量PK) 目录 输出结果 设计思路 核心代码 输出结果 设计思路 核心代码 # ...

最新文章

  1. 成长的速度一定要超过父母老去的速度
  2. 100%国产的AI操作系统,现在开源了!还有个AI版的App Store
  3. 私有5g网络_欧洲通过FUDGE5G的启动来支持工业4.0的云原生私有5G
  4. cumprod--累积连乘
  5. qstring转qchar_Qt 对QString操作
  6. 【Java基础】使用带有标签的break,跳出多层循环
  7. 原生JS数组去重的几种方法
  8. python装饰器函数执行后日志_python 装饰器理解
  9. [深入学习Redis]RedisAPI的原子性分析
  10. html file对象修改,HTML DOM
  11. python 获取网页元素_记一次python提取网页标签元素的坑
  12. Java 简单爬虫 代码
  13. 工程分析:Kconfig
  14. SYBASE公司的PowerDesigner下载与安装
  15. head标签中meta中IE=edge,chrome=1详解
  16. allegro 尺寸标注操作未到板边的处理
  17. sony的故事 第 18 章
  18. 如何下载Eclipse历史版本
  19. 城头土命适合做计算机电脑职业,土命人适合的职业
  20. 如何解决Mac电脑在启动时出现空白屏幕的情况?

热门文章

  1. Logback设置property参数
  2. android手机打电话时没有网络,4G手机打电话为什么会断网 4G上网和通话不能并存原因分析...
  3. 美年旅游_自由行_自由行分页PageHelper
  4. origin柱状图坐标标签_origin菜鸟求助。如何做横坐标连续(如下图)的柱状图?...
  5. 台式计算机关机后自行重启,台式电脑关机后自动重启怎么办?台式电脑关机后自动开机的处理办法...
  6. linux 多个ftp站点,vsftp在虚拟主机上建立多个ftp站点
  7. CF633H Fibonacci-ish II(莫队+线段树)
  8. javaweb项目实训总结_JAVA WEB实训总结
  9. 最通俗易懂的讲解工厂模式
  10. vue文字首尾相连无限轮播