再回顾SGX初始化(二)——uRTS端构建Enclave
目录
__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)。 线程上下文所用到的内存的布局
、通用线程上下文布局、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相关推荐
- SGX初始化中ELF文件解析
先记 ElfParser::run_parser()函数是SGX初始化<再回顾sgx_create_enclave>慢慢长征路的中间一环.比较独立又有些复杂,单独抽出来讲. ELF文件布局 ...
- 花书+吴恩达深度学习(二十)构建模型策略(超参数调试、监督预训练、无监督预训练)
目录 0. 前言 1. 学习率衰减 2. 调参策略 3. 贪心监督预训练 4. 贪心逐层无监督预训练 如果这篇文章对你有一点小小的帮助,请给个关注,点个赞喔~我会非常开心的~ 花书+吴恩达深度学习(十 ...
- 小白入门计算机视觉系列——ReID(二):baseline构建:基于PyTorch的全局特征提取网络(Finetune ResNet50+tricks)
ReID(二):baseline构建:基于PyTorch的全局特征提取网络(Finetune ResNet50+tricks) 本次带来的是计算机视觉中比较热门的重点的一块,行人重识别(也叫Perso ...
- C. DS二叉平衡树构建(教材版)
[id:157][20分]C. DS二叉平衡树构建 题目描述 在初始为空的平衡二叉树中依次插入n个结点,请输出最终的平衡二叉树. 要求实现平衡二叉树,不可以使用各类库函数. AVL代码参考模板: #i ...
- 【Android 逆向】Android 逆向通用工具开发 ( PC 端工程分析 | 网络初始化操作 | PC 端工程核心业务逻辑 )
文章目录 前言 一.网络初始化操作 二.PC 端工程核心业务逻辑 三.博客资源 前言 本篇博客重点分析 PC 端 hacktool 模块 ; 一.网络初始化操作 HackCommand::Prepar ...
- 【Android 逆向】Android 逆向通用工具开发 ( 网络模块开发 | SOCKET 网络套接字初始化 | 读取远程端 “Android 模拟器“ 信息 | 向远程端写出数据 )
文章目录 前言 一.SOCKET 网络套接字初始化 二.SOCKET 网络套接 读取远程端 ( Android 模拟器 ) 信息 三.SOCKET 网络套接 向远程端 ( Android 模拟器 ) ...
- Maven学习总结(二)——Maven项目构建过程练习
2019独角兽企业重金招聘Python工程师标准>>> Maven学习总结(二)--Maven项目构建过程练习 上一篇只是简单介绍了一下maven入门的一些相关知识,这一篇主要是体验 ...
- 【博客项目】—案例初始化(二)
[博客项目]-案例初始化(二) 构建模块化路由 构建博客管理页面模板 渲染模板
- GPS定位系统(二)——Android端
前言 GPS系列--Android端,github项目地址 tag: gps_mine Android移动端,主要是使用高德地图定位,后台上传定位信息,然后就是想办法尽量保活. 包括两个小功能:1.上 ...
- ML之分类预测之ElasticNet:利用ElasticNet回归对二分类数据集构建二分类器(DIY交叉验证+分类的两种度量PK)
ML之分类预测之ElasticNet:利用ElasticNet回归对二分类数据集构建二分类器(DIY交叉验证+分类的两种度量PK) 目录 输出结果 设计思路 核心代码 输出结果 设计思路 核心代码 # ...
最新文章
- 成长的速度一定要超过父母老去的速度
- 100%国产的AI操作系统,现在开源了!还有个AI版的App Store
- 私有5g网络_欧洲通过FUDGE5G的启动来支持工业4.0的云原生私有5G
- cumprod--累积连乘
- qstring转qchar_Qt 对QString操作
- 【Java基础】使用带有标签的break,跳出多层循环
- 原生JS数组去重的几种方法
- python装饰器函数执行后日志_python 装饰器理解
- [深入学习Redis]RedisAPI的原子性分析
- html file对象修改,HTML DOM
- python 获取网页元素_记一次python提取网页标签元素的坑
- Java 简单爬虫 代码
- 工程分析:Kconfig
- SYBASE公司的PowerDesigner下载与安装
- head标签中meta中IE=edge,chrome=1详解
- allegro 尺寸标注操作未到板边的处理
- sony的故事 第 18 章
- 如何下载Eclipse历史版本
- 城头土命适合做计算机电脑职业,土命人适合的职业
- 如何解决Mac电脑在启动时出现空白屏幕的情况?
热门文章
- Logback设置property参数
- android手机打电话时没有网络,4G手机打电话为什么会断网 4G上网和通话不能并存原因分析...
- 美年旅游_自由行_自由行分页PageHelper
- origin柱状图坐标标签_origin菜鸟求助。如何做横坐标连续(如下图)的柱状图?...
- 台式计算机关机后自行重启,台式电脑关机后自动重启怎么办?台式电脑关机后自动开机的处理办法...
- linux 多个ftp站点,vsftp在虚拟主机上建立多个ftp站点
- CF633H Fibonacci-ish II(莫队+线段树)
- javaweb项目实训总结_JAVA WEB实训总结
- 最通俗易懂的讲解工厂模式
- vue文字首尾相连无限轮播