33. 软件保护扩展 (SGX)¶
33.1. 概述¶
软件保护扩展(SGX)硬件允许用户空间应用程序预留代码和数据的私有内存区域
特权级(ring-0)ENCLS 函数负责协调这些区域的构建。
非特权级(ring-3)ENCLU 函数允许应用程序进入并在这些区域内部执行。
这些内存区域被称为 enclave。只能通过一组固定的入口点进入 enclave。每个入口点在同一时间只能容纳一个硬件线程。虽然 enclave 是通过使用 ENCLS 函数从常规二进制文件加载的,但只有 enclave 内部的线程才能访问其内存。CPU 会拒绝来自外部对该区域的访问,并且在数据离开 LLC 之前对其进行加密。
可以通过以下方式确定是否支持:
grep sgx /proc/cpuinfo
处理器必须支持 SGX 并且必须在 BIOS 中启用。如果某个具备硬件支持的系统显示不支持 SGX,请确保已在 BIOS 中启用该支持。如果 BIOS 为 SGX 提供了“Enabled(已启用)”和“Software Enabled(软件启用)”两种模式的选择,请选择“Enabled”。
33.2. Enclave 页缓存¶
SGX 利用 Enclave 页缓存 (EPC) 来存储与 enclave 关联的页面。它包含在 BIOS 保留的物理内存区域中。与用于常规内存的页面不同,在 enclave 构建期间,只有通过特殊的、受限制的 SGX 指令才能从 enclave 外部访问这些页面。
只有在 enclave 内部执行的 CPU 才能直接访问 enclave 内存。但是,在 enclave 内部执行的 CPU 可以访问 enclave 外部的普通内存。
内核对 enclave 内存的管理方式类似于对设备内存的处理方式。
33.2.1. Enclave 页面类型¶
- SGX Enclave 控制结构 (SECS)
Enclave 的地址范围、属性和其他全局数据都由该结构定义。
- 常规页面 (REG)
常规 EPC 页面包含 enclave 的代码和数据。
- 线程控制结构 (TCS)
线程控制结构页面定义了 enclave 的入口点,并跟踪 enclave 线程的执行状态。
- 版本数组 (VA)
版本数组页面包含 512 个槽位,每个槽位可以包含一个从 EPC 中换出的页面的版本号。
33.2.2. Enclave 页缓存映射¶
处理器在一个名为 Enclave 页缓存映射 (EPCM) 的硬件元数据结构中跟踪 EPC 页面。EPCM 包含每个 EPC 页面的一个条目,其中描述了所属的 enclave、访问权限和页面类型等信息。
EPCM 权限与常规页表是分开的。这可以防止内核(例如)允许对 enclave 希望保持只读的数据进行写入。EPCM 权限只能在常规 x86 页权限的基础上施加额外的限制。
实际上,SGX 架构允许处理器随时使所有 EPCM 条目失效。这就要求软件必须准备好随时处理 EPCM 错误。在实践中,这种情况可能发生在电源转换等事件中,此时用于加密 enclave 内存的临时密钥会丢失。
33.3. 应用程序接口¶
33.3.1. Enclave 构建函数¶
除了传统的编译器和链接器构建过程之外,SGX 还有一个单独的 enclave“构建”过程。Enclave 必须在执行(进入)之前构建。构建 enclave 的第一步是打开 /dev/sgx_enclave 设备。由于 enclave 内存受到保护而无法直接访问,因此随后需要使用特殊的特权指令将数据复制到 enclave 页面中并建立 enclave 页面权限。
-
long sgx_ioc_enclave_create(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_CREATE的处理程序
参数
struct sgx_encl *enclenclave 指针。
void __user *argioctl 参数。
描述
为 enclave 分配内核数据结构并调用 ECREATE。
返回
0:成功。
-EIO:ECREATE 失败。
-errno:POSIX 错误。
-
long sgx_ioc_enclave_add_pages(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_ADD_PAGES的处理程序
参数
struct sgx_encl *enclenclave 指针
void __user *arg指向
struct sgx_enclave_add_pages实例的用户空间指针
描述
向未初始化的 enclave 添加一个或多个页面,并可选地使用该页面的内容扩展度量值。SECINFO 和度量掩码应用于所有页面。
TCS 的 SECINFO 要求始终包含零权限,因为 CPU 会静默将它们清零。允许任何其他值都会导致度量值不匹配。
mmap() 的保护位受页面权限的上限限制。对于每个页面地址,最大保护位是通过以下启发式方法计算的:
常规页面:PROT_R、PROT_W 和 PROT_X 与 SECINFO 权限相匹配。
TCS 页面:PROT_R | PROT_W。
不允许 mmap() 超出给定地址范围内最大保护位的最小值。
在以下任何情况下,该函数将取消初始化 enclave 的内核数据结构并返回 -EIO:
保存 enclave 的物理内存 Enclave 页缓存 (EPC) 已失效。这将导致 EADD 和 EEXTEND 失败。
如果在执行 EADD 时源地址以某种方式损坏。
返回
0:成功。
-EACCES:源页面位于 noexec 分区中。
-ENOMEM:EPC 页面耗尽。
-EINTR:在处理数据之前调用被中断。
- -EIO:由于源地址无效
或电源循环导致 EADD 或 EEXTEND 失败。
-errno:POSIX 错误。
-
long sgx_ioc_enclave_init(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_INIT的处理程序
参数
struct sgx_encl *enclenclave 指针
void __user *arg指向
struct sgx_enclave_init实例的用户空间指针
描述
刷新所有未完成的已排队 EADD 操作并执行 EINIT。根据需要重写启动 Enclave 公钥哈希 MSR,使其与根据提供的 sigstruct 计算出的 enclave 的 MRSIGNER 相匹配。
返回
0:成功。
-EPERM:无效的 SIGSTRUCT。
-EIO:由于电源循环,EINIT 失败。
-errno:POSIX 错误。
-
long sgx_ioc_enclave_provision(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_PROVISION的处理程序
参数
struct sgx_encl *enclenclave 指针
void __user *arg指向
struct sgx_enclave_provision实例的用户空间指针
描述
通过提供指向 /dev/sgx_provision 的文件句柄,允许 enclave 使用 ATTRIBUTE.PROVISION_KEY。
返回
0:成功。
-errno:其他情况。
33.3.2. Enclave 运行时管理¶
支持 SGX2 的系统还支持对已初始化的 enclave 进行更改:修改 enclave 页面权限和类型,以及动态添加和删除 enclave 页面。当 enclave 访问其地址范围内没有后备页面的地址时,将动态向 enclave 添加一个新的常规页面。在可以使用该新页面之前,enclave 仍然需要在该新页面上运行 EACCEPT。
-
long sgx_ioc_enclave_restrict_permissions(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS的处理程序
参数
struct sgx_encl *enclenclave 指针
void __user *arg指向
struct sgx_enclave_restrict_permissions实例的用户空间指针
描述
对于属于已初始化 enclave(在 SGX_IOC_ENCLAVE_INIT 之后)的页面,SGX2 区分了放宽和限制由硬件维护的 enclave 页面权限(EPCM 权限)。
无法从 enclave 内部限制 EPCM 权限,enclave 需要内核运行特权级 0 指令 ENCLS[EMODPR] 和 ENCLS[ETRACK]。硬件会忽略通过此调用放宽 EPCM 权限的尝试。
返回
0:成功
-errno:其他情况
-
long sgx_ioc_enclave_modify_types(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_MODIFY_TYPES的处理程序
参数
struct sgx_encl *enclenclave 指针
void __user *arg指向
struct sgx_enclave_modify_types实例的用户空间指针
描述
更改 enclave 页面类型的能力支持以下用例
可以通过将常规页面(
SGX_PAGE_TYPE_REG)的类型更改为 TCS(SGX_PAGE_TYPE_TCS)页面来向 enclave 添加 TCS 页面。有了此支持,已初始化的 enclave 所支持的线程数可以动态增加。可以通过将页面类型更改为
SGX_PAGE_TYPE_TRIM,从已初始化的 enclave 中动态删除常规页面或 TCS 页面。将页面类型更改为SGX_PAGE_TYPE_TRIM会将该页面标记为待删除,实际删除操作则由在 enclave 内部对SGX_PAGE_TYPE_TRIM页面运行 ENCLU[EACCEPT] 之后调用的SGX_IOC_ENCLAVE_REMOVE_PAGESioctl() 的处理程序来完成。
返回
0:成功
-errno:其他情况
-
long sgx_ioc_enclave_remove_pages(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_REMOVE_PAGES的处理程序
参数
struct sgx_encl *enclenclave 指针
void __user *arg指向
struct sgx_enclave_remove_pages实例的用户空间指针
描述
从已初始化的 enclave 中删除页面的流程的最后一步。完整流程如下
用户使用
SGX_IOC_ENCLAVE_MODIFY_TYPESioctl() 将要删除的页面的类型更改为SGX_PAGE_TYPE_TRIM。用户通过从 enclave 内部运行 ENCLU[EACCEPT] 来批准页面删除。
用户使用在此处处理的
SGX_IOC_ENCLAVE_REMOVE_PAGESioctl() 启动实际的页面删除。
首先删除指向该页面的任何页表条目,然后继续实际删除 enclave 页面及支持它的数据。
VA 页面不受此删除操作的影响。因此,enclave 最终可能会拥有比支持其所有页面所需的更多的 VA 页面。
返回
0:成功
-errno:其他情况
33.3.3. Enclave vDSO¶
进入 enclave 只能通过 SGX 特定的 EENTER 和 ERESUME 函数完成,这是一个不简单的过程。由于进出 enclave 转换的复杂性,enclave 通常利用库来处理实际的转换。这大致类似于大多数应用程序如何使用 glibc 实现来封装系统调用。
enclave 的另一个关键特征是它们可以在其正常操作过程中生成需要在 enclave 中处理的异常,或者是 SGX 特有的异常。
SGX 可以利用 vDSO 提供的特殊异常修复,而不是使用传统的信号机制来处理这些异常。内核提供的 vDSO 函数封装了进出 enclave 的底层转换,例如 EENTER 和 ERESUME。vDSO 函数会拦截原本会生成信号的异常,并将错误信息直接返回给其调用者。这避免了处理信号处理程序的繁琐工作。
-
vdso_sgx_enter_enclave_t¶
Typedef:
__vdso_sgx_enter_enclave()的原型,这是一个用于进入 SGX enclave 的 vDSO 函数。
语法
int vdso_sgx_enter_enclave_t (unsigned long rdi, unsigned long rsi, unsigned long rdx, unsigned int function, unsigned long r8, unsigned long r9, struct sgx_enclave_run *run)
参数
unsigned long rdiRDI 的透传值
unsigned long rsiRSI 的透传值
unsigned long rdxRDX 的透传值
unsigned int functionENCLU 函数,必须是 EENTER 或 ERESUME
unsigned long r8R8 的透传值
unsigned long r9R9 的透传值
struct sgx_enclave_run *runstruct sgx_enclave_run,必须非 NULL
注意
__vdso_sgx_enter_enclave() 不保证完全符合 x86-64 ABI,例如不处理 XSAVE 状态。除了非易失性通用寄存器、EFLAGS.DF 和 RSP 对齐之外,根据 x86-64 ABI 保持/设置状态是 enclave 及其运行时的责任,也就是说,如果不经过 enclave 及其运行时的仔细考虑,就不能从 C 代码中调用 __vdso_sgx_enter_enclave()。
除 RAX、RBX 和 RCX 外的所有通用寄存器都原封不动地传递给 enclave。RAX、RBX 和 RCX 被 EENTER 和 ERESUME 消耗,并分别加载了 function、异步出口指针和 run.tcs。
RBP 和栈用于将 __vdso_sgx_enter_enclave() 固定到进入 enclave 之前的状态,例如在 enclave 退出后检索 run.exception 和 run.user_handler。所有其他寄存器均可供 enclave 及其运行时使用,例如,enclave 可以将附加数据压入栈中(并修改 RSP)以将信息传递给可选的用户处理程序(见下文)。
在 ENCLU 上报告的大多数异常(包括在 enclave 内部发生的异常)都会被修复并同步报告,而不是通过标准信号传递。调试异常 (#DB) 和断点 (#BP) 绝不会被修复,并且总是通过标准信号传递。对于同步报告的异常,将返回 -EFAULT,并且有关异常的详细信息将记录在 run.exception(可选的 sgx_enclave_exception 结构体)中。
返回
0:ENCLU 函数成功执行。
-EINVAL:无效的 ENCL 编号(既不是 EENTER 也不是 ERESUME)。
33.4. ksgxd¶
SGX 支持包含一个名为 ksgxd 的内核线程。
33.4.1. EPC 清理¶
ksgxd 在 SGX 初始化时启动。当处理器上电或重置时,enclave 内存通常就可以使用了。但是,如果 SGX 自重置以来一直在使用,enclave 页面可能会处于不一致状态。例如,这可能发生在崩溃和 kexec() 循环之后。在启动时,ksgxd 会重新初始化所有 enclave 页面,以便它们可以被分配和重复使用。
清理工作通过遍历 EPC 地址空间并对每个物理页面应用 EREMOVE 函数来完成。一些 enclave 页面(如 SECS 页面)对其他页面具有硬件依赖性,这会阻止 EREMOVE 发挥作用。执行两次 EREMOVE 遍历可以消除这些依赖关系。
33.4.2. 页面回收器¶
与核心的 kswapd 类似,ksgxd 负责管理 enclave 内存的超额分配。如果系统 enclave 内存耗尽,ksgxd 会将 enclave 内存“交换”到普通内存。
33.5. 启动控制¶
SGX 提供了一种启动控制机制。在复制了所有 enclave 页面之后,内核执行 EINIT 函数,该函数初始化 enclave。只有在此之后,CPU 才能在 enclave 内部执行。
EINIT 函数获取 enclave 度量值的 RSA-3072 签名。该函数检查度量值是否正确,并且签名是否使用哈希到四个代表公钥 SHA256 的 IA32_SGXLEPUBKEYHASH{0, 1, 2, 3} MSR 的密钥进行签名。
这些 MSR 可以由 BIOS 配置为可读或可写。Linux 仅支持可写配置,以便让内核对启动控制策略拥有完全控制权。在调用 EINIT 函数之前,驱动程序会设置 MSR 以匹配 enclave 的签名密钥。
33.6. 加密引擎¶
为了在 enclave 数据离开 CPU 封装时对其进行隐藏,内存控制器配有一个加密引擎,用于透明地加密和解密 enclave 内存。
在 Ice Lake 之前的 CPU 中,使用内存加密引擎 (MEE) 来加密离开 CPU 缓存的页面。MEE 使用根在 SRAM 中的 n 叉默克尔树来维护加密数据的完整性。这提供了完整性和防重放保护,但无法扩展到较大的内存大小,因为更新默克尔树所需的时间与内存大小呈对数增长。
从 Ice Lake 开始的 CPU 使用全内存加密 (TME) 来代替 MEE。基于 TME 的 SGX 实现没有完整性默克尔树,这意味着无法缓解完整性和重放攻击问题。不过,它包含了一些额外的更改,以防止返回密文以及创建软件内存别名。
在 MEE 和 TME 系统上,对 enclave 内存的 DMA 都会被范围寄存器阻止(SDM 第 41.10 节)。
33.7. 使用模型¶
33.7.2. 应用程序容器¶
应用程序可以加载到容器 enclave 中,该容器 enclave 配有专门配置的库 OS 和允许应用程序运行的运行时。当线程进入 enclave 时,enclave 运行时和库 OS 协同工作以执行应用程序。
33.8. 潜在内核 SGX 漏洞的影响¶
33.8.1. EPC 泄漏¶
当发生 EPC 页面泄漏时,dmesg 中会显示如下 WARNING
“EREMOVE returned ... and an EPC page was leaked. SGX may become unusable...”
这实际上是内核对 EPC 页面的释放后使用,并且由于 SGX 的工作方式,该漏洞是在释放时被检测到的。为了避免将来出现更多错误,内核没有将该页面添回可用 EPC 页面的池中,而是有意泄漏了该页面。
发生这种情况时,内核可能很快会泄漏更多 EPC 页面,并且由于 SGX 可用的内存有限,SGX 很可能会变得不可用。但是,虽然这对 SGX 可能是致命的,但内核的其他部分不太可能受到影响,并且应该会继续正常工作。
因此,当发生这种情况时,用户应停止运行任何新的 SGX 工作负载(或任何新工作负载),并迁移所有有价值的工作负载。虽然重新启动机器可以恢复所有 EPC 内存,但应向 Linux 开发者报告此漏洞。
33.9. 虚拟 EPC¶
该实现还具有一个虚拟 EPC 驱动程序,用于支持客户机中的 SGX enclave。与 SGX 驱动程序不同,由虚拟 EPC 驱动程序分配的 EPC 页面没有与之关联的特定 enclave。这是因为 KVM 不跟踪客户机如何使用 EPC 页面。
因此,SGX 核心页面回收器不支持回收通过虚拟 EPC 驱动程序分配给 KVM 客户机的 EPC 页面。如果用户希望在同一台机器的宿主机和客户机中同时部署 SGX 应用程序,则应为宿主机 SGX 应用程序保留足够的 EPC(从物理 EPC 大小中减去所有 SGX 虚拟机总的虚拟 EPC 大小),以便它们能够以可接受的性能运行。
架构行为是在客户机重置后也将所有 EPC 页面恢复到未初始化状态。由于只能通过特权指令 ENCLS[EREMOVE] 达到此状态,因此 /dev/sgx_vepc 提供了 SGX_IOC_VEPC_REMOVE_ALL ioctl,以便在虚拟 EPC 中的所有页面上执行该指令。
EREMOVE 可能会由于三种原因而失败. 用户空间必须注意预期的失败并进行如下处理
当任何线程在页面所属的 enclave 中运行时,页面删除将始终失败。在这种情况下,无论是否成功删除了某些页面,ioctl 都会返回
EBUSY;用户空间可以通过阻止执行任何映射了虚拟 EPC 的 vcpu 来避免这些失败。如果针对引用相同“SECS”元数据页面的页面并发进行两次
EREMOVE调用,页面删除将导致一般保护错误。如果并发调用SGX_IOC_VEPC_REMOVE_ALL,或者客户机中的/dev/sgx_vepc文件描述符与SGX_IOC_VEPC_REMOVE_ALL同时关闭,则可能会发生这种情况;它也将被报告为EBUSY。这可以在用户空间中通过对 ioctl() 和 close() 的调用进行序列化来避免,但总体而言它不应该是个问题。最后,对于仍有子页面的 SECS 元数据页面,页面删除将会失败。可以通过在映射到客户机的所有
/dev/sgx_vepc文件描述符上执行SGX_IOC_VEPC_REMOVE_ALL来删除子页面。这意味着必须调用两次 ioctl():一组初始调用用于删除子页面,随后的一组调用用于删除 SECS 页面。第二组调用仅对第一次调用返回非零值的那些映射是必需的。如果第二轮SGX_IOC_VEPC_REMOVE_ALL调用的返回值不是 0,则表明内核或用户空间客户端中存在漏洞。