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的完全妥协。
最后,虚拟机监控程序是可信的——我们必须假设其行为正确且非恶意。
图1:AWS Nitro Enclaves系统的简化模型
虚拟套接字(Vsocks)
Enclave的主要入口点是本地虚拟套接字(vsock)。只有父EC2实例可以使用该套接字。Vsocks由虚拟机监控程序管理——虚拟机监控程序为父EC2实例和enclave的内核提供/dev/vsock设备节点。
Vsocks通过上下文标识符(CID)和端口进行标识。每个enclave必须使用唯一的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(监听数据)。VMADDR_CID_PARENT的示例用法可以在AWS的enclaves SDK的init.c模块中找到——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源代码,虚拟机监控程序强制执行以下操作:
- 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软件)。但请注意,我们不知道CAT是否可以为运行的enclave动态更改——这将使此解决方案无用。
如果您实施上述任何缓解措施,则必须向认证添加相关信息。否则,用户将无法确保L3侧信道攻击向量真正得到缓解。
无论如何,您希望安全关键代码(如密码学)以实现与秘密无关的内存访问模式实现。硬件和软件级安全控制在这里都很重要。
内存
Enclaves的内存从父EC2实例中划分出来。保护对enclave内存的访问并在返回给父节点后清除它是虚拟机监控程序的责任。当将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可以依赖可信虚拟机监控程序来提供安全时钟源。从开发人员的角度来看,只有三个操作值得采取以提高enclave时间源的安全性和正确性:
- 确保在enclave的内核配置中将current_clocksource设置为kvm-clock;甚至考虑添加应用程序级运行时时钟检查(以防在enclave引导期间出现问题,最终使用不同的时钟源)。
- 启用精确时间协议以更好地同步enclave和虚拟机监控程序之间的时钟。它类似于网络时间协议(NTP),但通过硬件连接工作。它应该比NTP更安全(因为攻击面更小)且更易于设置。
- 对于安全关键功能(如重放保护),使用Unix时间。小心UTC和时区,因为夏令时和闰秒可能"使时间向后移动"。
为什么选择kvm-clock?
使用x86架构的机器可以有几种不同的时间源。我们可以使用以下命令检查enclaves可用的源:
|
|
Enclaves应该有两个源:tsc和kvm-clock(如果您运行示例enclave并检查其源,可以看到它们);后者默认启用,可以在current_clocksource文件中检查。这些源如何工作?
TSC机制基于时间戳计数器寄存器。它是作为模型特定寄存器(MSR)实现的每CPU单调计数器。每个(虚拟)CPU都有自己的寄存器。计数器随每个CPU周期递增(或多或少)。Linux基于按CPU频率缩放计数器和某个初始日期来计算当前时间。
如果我们具有root权限,可以读取(和写入!)TSC值。为此,我们需要TSC的偏移量(为16)和其大小(为8字节)。可以通过/dev/cpu设备访问MSR寄存器:
|
|
TSC也可以使用clock_gettime方法和CLOCK_MONOTONIC_RAW时钟ID读取,以及使用RDTSC汇编指令读取。
理论上,如果我们更改TSC,则clock_gettime与CLOCK_REALTIME时钟ID、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虚拟机监控程序)向客户机(enclave)提供pvclock_vcpu_time_info结构。该结构包含使客户机能够调整其时间测量的信息——最显著的是主机的挂钟(system_time字段),用作初始日期。
有趣的是,即使启用了kvm-clock,客户机的用户空间应用程序也可以使用TSC机制。这是因为RDTSC指令(通常)不被模拟,因此可能提供未调整的TSC寄存器读数。
请注意,如果您的enclaves使用不同的时钟源或启用NTP,您应该进行一些额外研究以查看是否存在相关的安全问题。
认证
加密认证是最终用户的信任来源。用户正确解析和验证认证至关重要。幸运的是,AWS提供了关于如何使用认证的良好文档。
最重要的认证数据是协议特定的,但我们有一些普遍适用的提示供开发人员牢记(除了AWS文档中编写的内容):
- Enclave应强制执行最小nonce长度。
- 用户应检查认证中提供的时间戳以及nonce。
- 认证的时间戳不应用于推断enclave的时间。此时间戳可能与enclave的时间不同,因为前者由虚拟机监控程序生成,而后者由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编码的