AWS Nitro Enclaves安全笔记:攻击面分析
在云应用安全竞赛中,AWS Nitro Enclaves已成为隔离敏感工作负载的强大工具。但能力越大责任越大——同时也存在潜在的安全隐患。作为机密计算安全领域的先驱,Trail of Bits团队深入研究了AWS Nitro Enclaves的攻击面,发现了可能危及这些强化环境的潜在漏洞。
本文提炼了我们来之不易的见解,为部署Nitro Enclaves的开发人员提供可操作的指导。阅读后,您将能够:
- 识别并缓解 enclave 部署中的关键安全风险
- 实现随机性、侧信道保护和时间管理的最佳实践
- 避免虚拟套接字处理和证明中的常见陷阱
我们将涵盖以下主题:
- 虚拟套接字安全
- 随机性和熵源
- 侧信道攻击缓解
- 内存管理
- 时间源注意事项
- 证明最佳实践
- NSM驱动程序安全
无论您是Nitro Enclaves的新手,还是希望强化现有部署,本指南都将帮助您驾驭AWS上机密计算的独特安全格局。
简要威胁模型
首先是一个简短的威胁模型。Enclaves可能受到父Amazon EC2实例的攻击,这是唯一能直接访问enclave的组件。在攻击enclave的背景下,我们应假设父实例的内核(包括其nitro_enclaves驱动程序)已被攻击者控制。来自实例的DoS攻击并不值得担忧,因为父实例始终可以关闭其enclaves。
如果EC2实例从互联网转发用户流量,那么对其enclaves的攻击可能来自该方向,并可能涉及所有常见的攻击向量(业务逻辑、内存破坏、加密等)。反之,用户可能成为恶意EC2实例通过冒充攻击的目标。
在信任区域方面,enclave应被视为单个信任区域。Enclaves运行正常的Linux,理论上可以使用其访问控制功能在内部"划分界限"。但这样做毫无意义——对enclave内部任何内容的对抗性访问(例如通过供应链攻击)将削弱其强隔离和证明的好处。因此,单个enclave组件的泄露应被视为整个enclave的完全泄露。
最后,虚拟机监控程序(hypervisor)是可信的——我们必须假设其行为正确且非恶意。
图1:AWS Nitro Enclaves系统的简化模型
虚拟套接字(Vsocks)
Enclave的主要入口点是本地虚拟套接字(vsock)。只有父EC2实例可以使用该套接字。Vsocks由虚拟机监控程序管理——hypervisor为父EC2实例和enclave的内核提供/dev/vsock
设备节点。
Vsocks通过上下文标识符(CID)和端口进行标识。每个enclave必须使用唯一的CID,该CID可在初始化期间设置,并可以监听多个端口。有几个预定义的CID:
VMADDR_CID_HYPERVISOR = 0
VMADDR_CID_LOCAL = 1
VMADDR_CID_HOST = 2
VMADDR_CID_PARENT = 3
(父EC2实例)VMADDR_CID_ANY = 0xFFFFFFFF = -1U
(监听所有CID)
Enclaves通常仅使用VMADDR_CID_PARENT
CID(发送数据)和VMADDR_CID_ANY
CID(监听数据)。在AWS的enclaves SDK的init.c
模块中可以找到使用VMADDR_CID_PARENT
的示例——enclave在初始化后立即向父EC2实例发送"心跳"信号。该信号由nitro-cli工具处理。
标准套接字相关问题是vsocks主要需要关注的问题。开发enclave时,请考虑以下事项以确保此类问题不会启用某些攻击向量:
- Enclave是否异步接受连接(使用多线程)?如果不是,单个用户可能会长时间阻止其他用户访问enclave。
- Enclave是否对连接设置超时?如果没有,单个用户可能会持续占用套接字或向enclave打开多个连接,从而耗尽可用资源(如文件描述符)。
- 如果enclave使用多线程,其状态同步是否正确实现?
- Enclave是否正确处理错误?使用recv方法从套接字读取尤其棘手。常见的模式是循环调用recv直到收到所需字节数,但应仔细实现此模式:
- 如果返回EINTR错误,enclave应重试recv调用。否则,enclave可能会丢弃有效且活跃的连接。
- 如果没有错误但返回长度为0,enclave应中断循环。否则,对等方可能在发送预期字节数之前关闭连接,使enclave无限循环。
- 如果套接字是非阻塞的,正确读取数据则更加棘手。
这些问题的主要风险是DoS。父EC2实例可以关闭其任何enclaves,因此实际风险仅存在于外部用户可以触发DoS的情况下。及时访问系统是enclave和与enclave通信的EC2实例的共同责任。
涉及vsocks的另一类漏洞是CID混淆:如果EC2实例运行多个enclaves,它可能将数据发送到错误的enclave(例如,由于竞争条件问题)。然而,即使存在此类错误,也不应构成太大风险或对enclave的攻击面贡献太多,因为用户和enclave之间的流量应进行端到端身份验证。
最后,请注意enclaves默认使用SOCK_STREAM套接字类型。如果将类型更改为SOCK_DGRAM,请进行一些研究以了解此通信类型的安全属性。
随机性
Enclaves必须能够访问安全随机性。此处的"安全"意味着对手不知道或控制用于生成随机数据的所有熵。在Linux上,内核将几个熵源混合在一起。其中包括CPU提供的RDRAND/RDSEED源和平台提供的硬件随机数生成器(RNG)。AWS Nitro可信平台模块提供自己的硬件RNG(称为nsm-hwrng)。
图2:Linux内核中的随机性源
最终随机性可以通过getrandom系统调用或从(不太可靠的)/dev/{u}random
设备获取。还有/dev/hwrng
设备,它提供对所选硬件RNG的更直接访问。用户空间应用程序不应使用此设备。
当内核注册新的硬件RNG时,它会立即用于向系统添加熵。可用硬件RNG的列表可在/sys/class/misc/hw_random/rng_available
文件中找到。注册的RNG之一被自动选择以定期添加熵,并在/sys/devices/virtual/misc/hw_random/rng_current
文件中指示。
我们建议配置您的enclaves以显式检查当前RNG(rng_current)是否设置为nsm-hwrng。此检查将确保AWS Nitro RNG已成功注册,并且是内核定期用于添加熵的RNG。
为了进一步增强enclave随机性的安全性,请在有方便可用的源时从外部源提取熵。常见的外部源是AWS密钥管理服务,它提供了一个方便的GenerateRandom方法,enclaves可以使用该方法通过加密通道引入熵。
如果您想遵循NIST/AIS标准(参见"Linux随机数生成器文档和分析"中的第5.3.1节)或怀疑RDRAND/RDSEED指令存在问题(另请参阅此LWNet文章和此推文),可以禁用random.trust_{bootloader,cpu}
内核参数。这将通知内核不包括这些源以估计可用熵。
最后,确保您的enclaves使用大于5.17.12的内核版本——内核的随机算法引入了重要更改。
侧信道
应用程序级时序侧信道攻击对enclaves构成威胁,就像对任何应用程序一样。在enclaves内部运行的应用程序必须在恒定时间内处理机密数据。来自父EC2实例的攻击可以使用几乎系统时钟精确的时间测量,因此不要依赖网络抖动进行缓解。您可以在我们的博客文章"优化屏障的生命周期"中阅读有关时序攻击向量的更多信息。
此外,尽管这并不真正构成侧信道攻击,但enclave返回的错误消息可能被攻击者用于推理enclave的状态。考虑诸如填充oracle和账户枚举等问题。我们建议尽可能保持enclave返回的错误通用。错误应多通用取决于给定的业务需求,因为任何应用程序的用户都需要某种程度的错误跟踪。
CPU内存侧信道
需要了解的主要侧信道攻击类型涉及CPU内存。CPU共享一些内存——最显著的是缓存行。如果内存同时可被来自不同信任区域的两个组件(如enclave及其父EC2实例)访问,那么一个组件可能通过测量内存访问模式间接泄漏另一个组件的数据。即使应用程序在恒定时间内处理秘密数据,访问此类侧信道的攻击者也可以利用数据相关的分支。
在典型架构中,CPU可分为NUMA节点、CPU核心和CPU线程。最小的物理处理单元是CPU核心。核心可能有多个逻辑线程(虚拟CPU)——最小的逻辑处理单元——线程共享L1和L2缓存行。L3行(也称为最后一级缓存)在NUMA节点中的所有核心之间共享。
图3:系统CPU排列示例,通过lstopo命令获得
父EC2实例可能仅从NUMA节点分配了少量CPU核心。因此,它们可能与其他实例共享L3缓存。然而,AWS白皮书"AWS Nitro系统的安全设计"声称L3缓存从未同时共享。不幸的是,关于此主题的信息不多。
图4:AWS白皮书摘录,说明具有最大CPU数量一半的实例应填充整个CPU核心(插槽?)
enclaves中的CPU呢?CPU从父EC2实例获取并分配给enclave。根据AWS和nitro-cli源代码,hypervisor强制执行以下操作:
- CPU #0核心(所有其线程)不可分配给enclaves。
- Enclaves必须使用完整核心。
- 分配给enclave的所有核心必须来自同一NUMA节点。
在最坏的情况下,enclave将与其父EC2实例(或其他enclaves)共享L3缓存。然而,L3缓存是否可用于执行侧信道攻击尚有争议。一方面,AWS白皮书并未对此攻击向量大做文章。另一方面,最近的研究表明此类攻击的实用性(参见"最后一级缓存侧信道攻击在现代公共云中的可行性")。
如果您非常担心L3缓存侧信道攻击,可以在完整的NUMA节点上运行enclave。为此,您必须向父EC2实例分配多个完整的NUMA节点,以便一个NUMA节点可用于enclave,同时在另一个NUMA节点上为父节点保留一些CPU。请注意,此缓解措施资源效率低且成本高。
或者,您可以尝试使用Intel的缓存分配技术(CAT)将enclave的L3缓存与父节点隔离(参见intel-cmt-cat软件)。但请注意,我们不知道是否可以为运行的enclave动态更改CAT——这将使此解决方案无效。
如果您实施上述任何缓解措施,则必须向证明添加相关信息。否则,用户将无法确保L3侧信道攻击向量真正得到缓解。
无论如何,您希望安全关键代码(如密码学)以与秘密无关的内存访问模式实现。硬件和软件级安全控制在这里都很重要。
内存
Enclaves的内存从父EC2实例中划分出来。保护对enclave内存的访问并在其返回给父节点后清除是hypervisor的责任。当将enclave内存作为攻击向量时,开发人员真正需要担心的只是DoS攻击。在enclave内部运行的应用程序应限制外部用户可以存储的数据量。否则,单个用户可能能够消耗enclave的所有可用内存并使enclave崩溃(尝试在enclave内运行cat /dev/zero
以查看消耗大量内存时的行为)。
那么您的enclave有多少空间?答案有点复杂。首先,enclave的init进程不会挂载新的根文件系统,而是保留初始initramfs并chroot到目录(尽管有一个待处理的PR,一旦合并将改变此行为)。这对文件系统的大小施加了一些限制。此外,保存在文件系统中的数据将消耗可用RAM。
您可以通过在enclave内执行free命令来检查总可用RAM和文件系统空间。文件系统的大小限制应约为总空间的40-50%。您可以通过填充整个文件系统空间并检查最终存储了多少数据来确认这一点:
|
|
内存的另一个问题是enclave没有任何持久存储。一旦关闭,其所有数据都会丢失。此外,AWS Nitro不提供任何特定的数据密封机制。实现它是您的应用程序的责任。阅读我们的博客文章"翻转位的轨迹"以获取更多信息。
时间
安全问题的较少见来源是enclave的时间源——即enclave从何处获取时间。能够控制enclave时间的攻击者可以执行回滚和重放攻击。例如,攻击者可以将enclave的时间切换到过去,并使enclave接受过期的TLS证书。
在机密计算领域,获取可信时间源可能是一个有些复杂的问题。幸运的是,enclaves可以依赖可信的hypervisor来提供安全时钟源。从开发人员的角度来看,只有三个操作值得采取以提高enclave时间源的安全性和正确性:
- 确保在enclave的内核配置中将current_clocksource设置为kvm-clock;甚至考虑添加应用程序级运行时时钟检查(以防在enclave引导过程中出现问题并最终使用不同的时钟源)。
- 启用精确时间协议以更好地同步enclave和hypervisor之间的时钟。它类似于网络时间协议(NTP),但通过硬件连接工作。它应比NTP更安全(因为攻击面更小)且更易于设置。
- 对于安全关键功能(如重放保护),使用Unix时间。小心UTC和时区,因为夏令时和闰秒可能"使时间倒流"。
为什么是kvm-clock?
使用x86架构的机器可以有几种不同的时间源。我们可以使用以下命令检查enclaves可用的源:
|
|
Enclaves应有两个源:tsc和kvm-clock(如果您运行示例enclave并检查其源,可以看到它们);后者默认启用,如current_clocksource文件中所示。这些源如何工作?
TSC机制基于时间戳计数器寄存器。它是一个每CPU单调计数器,实现为模型特定寄存器(MSR)。每个(虚拟)CPU都有自己的寄存器。计数器随每个CPU周期递增(或多或少)。Linux基于按CPU频率缩放计数器和某个初始日期来计算当前时间。
如果我们具有root权限,可以读取(和写入!)TSC值。为此,我们需要TSC的偏移量(为16)及其大小(为8字节)。可以通过/dev/cpu
设备访问MSR寄存器:
|
|
TSC也可以使用带有CLOCK_MONOTONIC_RAW时钟ID的clock_gettime方法以及RDTSC汇编指令读取。
理论上,如果我们更改TSC,则通过带有CLOCK_REALTIME时钟ID的clock_gettime、gettimeofday函数和date命令报告的挂钟时间应发生变化。然而,Linux内核努力使TSC行为合理并彼此同步(例如,查看tsc看门狗代码和与MSR_IA32_TSC_ADJUST寄存器相关的功能)。因此破坏时钟并不那么容易。
TSC可用于跟踪经过的时间,但enclaves从何处获取计算经过时间的"某个初始日期"?通常,在其他系统中,该日期使用NTP获取。然而,enclaves没有开箱即用的网络访问权限,也不使用NTP(参见AWS 2020 re:Invent会议此演示文稿的第26张幻灯片)。
图5:Enclave可能的时间源
使用tsc时钟且没有NTP,初始日期是随机选择的——事实上我们尚未确定其来源。您可以通过传递no-kvmclock no-kvmclock-vsyscall内核参数(但请注意这些参数不应在运行时提供)强制enclave在没有kvm-clock的情况下启动,并自行检查初始日期。在我们的实验中,日期是:
|
|
如您所见,TSC机制与enclaves配合不佳。此外,当机器虚拟化时,它会严重崩溃。因此,AWS引入了kvm-clock作为enclaves的默认时间源。它是半虚拟时钟驱动程序(pvclock)协议的实现(有关pvclock的更多信息,请参见此文章和此博客文章)。通过此协议,主机(在我们的案例中为AWS Nitro hypervisor)向客户机(enclave)提供pvclock_vcpu_time_info结构。该结构包含使客户机能够调整其时间测量的信息——最显著的是主机的挂钟(system_time字段),用作初始日期。
有趣的是,即使启用了kvm-clock,客户机的用户态应用程序也可以使用TSC机制。这是因为RDTSC指令(通常)不被模拟,因此可能提供未经调整的TSC寄存器读数。
请注意,如果您的enclaves使用不同的时钟源或启用NTP,您应进行一些额外研究以查看是否存在相关的安全问题。
证明
加密证明是最终用户的信任来源。用户正确解析和验证证明至关重要。幸运的是,AWS提供了关于如何使用证明的良好文档。
最重要的证明数据是协议特定的,但我们有一些普遍适用的提示供开发人员牢记(除了AWS文档中编写的内容):
- Enclave应强制执行最小随机数长度。
- 用户应检查证明中提供的时间戳以及随机数。
- 证明的时间戳不应用于推理enclave的时间。此时间戳可能不同于enclave的时间,因为前者由hypervisor生成,而后者由enclave使用的任何时钟源生成。
- 如果可能,不要将RSA用于public_key功能。
NSM驱动程序
您的enclave应用程序将使用NSM驱动程序,可通过/dev/nsm
节点访问。其源代码可在aws-nitro-enclaves-sdk-bootstrap和内核存储库中找到。应用程序通过IOCTL系统调用与驱动程序通信,并可以使用nsm-api库这样做。
开发人员应注意,在enclave内部运行的应用程序可能误用驱动程序或库。但是,如果开发人员采取这些步骤,不会出太多问题:
- 驱动程序允许您扩展和锁定比基本0-4和8 PCR更多的平台配置寄存器(PCR)。锁定的PCR无法扩展,它们包含在enclave证明中。这些附加PCR的使用方式取决于您如何配置应用程序。只需确保它区分锁定和未锁定的PCR。
- 记住在向NSM驱动程序发送DescribePCR请求时让应用程序检查PCR的锁定状态属性。否则,它可能咨询仍可能被操纵的PCR。
- 请求和响应是CBOR编码的。确保正确编码。错误解码的响应可能向您的应用程序提供虚假数据。
- 不建议直接使用nsm_get_random方法。它跳过内核混合多个熵源的算法,因此更容易出错。相反,使用常见的随机性API(如getrandom)。
- nsm_init方法在错误时返回-1,这在Rust中是不寻常的行为,因此请确保您的应用程序考虑到这一点。
总结
保护AWS Nitro Enclaves需要在多个攻击向量上保持警惕。通过实施本文中的建议——从强化虚拟套接字到验证随机性源——您可以显著降低enclave工作负载泄露的风险,帮助塑造机密计算更安全的未来。
关键要点:
- 将enclaves视为单个信任区域并实现端到端安全。
- 通过适当的CPU分配和恒定时间处理来缓解侧信道风险。
- 在运行时验证enclave熵源。
- 在enclave内部使用正确的时间源。
- 实施稳健的证明实践,包括随机数和时间戳验证。
有关更多安全注意事项,请参见我们关于enclave镜像和证明的第一篇文章。如果您的enclave使用外部系统——如AWS密钥管理服务或AWS证书管理器——请审查这些系统和支持工具以查找其他安全隐患。
我们鼓励您批判性地评估自己的Nitro Enclave部署。Trail of Bits为机密计算环境提供深入的安全评估和自定义强化策略。如果您准备将Nitro Enclaves的安全性提升到新水平,请联系我们安排与专家的咨询,并确保您的敏感工作负载真正保密。