沙箱安全:Cloudflare Workers 的安全加固(二)
虚拟内存并非无限,Linux 进程中会执行大量操作
不过,到这一步我们还没有完全解决问题。每个沙箱需要在进程的虚拟内存映射中预留 8 GiB 的空间,并且出于效率考虑,这个空间必须为 4 GiB 对齐。虽然它实际占用的物理内存要少得多,但沙箱机制为了实现其安全特性,需要这么大的虚拟地址空间。这就给我们带来了一个难题:因为在采用四级页表的 Linux 系统中,一个进程的虚拟地址空间“仅有”128 TiB(另外 128 TiB 被预留给了内核,用户空间无法使用)。
在 Cloudflare,我们希望尽可能高效地运行 Workers,以降低成本和价格,同时还会一并提供免费套餐版本。这意味着每台机器上都会运行大量隔离实例(每个沙箱一个),以至于很难将它们全部放入 128 TiB 的空间中。
基于这一认知,我们必须审慎规划沙箱在内存中的布局。但遗憾的是,Linux 系统调用 mmap 不允许我们直接指定内存分配的对齐方式——除非您能恰好猜到一个可用的空闲内存位置来发起请求。若要获取一个 4 GiB 对齐的 8 GiB 内存区域,我们必须申请 12 GiB 的空间,然后在该范围内找出必然存在的对齐 8 GiB 区域,并将未使用的(下方示图中阴影标注的)边缘归还给操作系统:
图2
如果我们允许 Linux 内核随机放置沙箱,最终就会形成如下带有间隙的布局。尤其是在系统运行一段时间后,沙箱之间可能会出现 8 GiB 和 4 GiB 两种大小的间隙。
图3
遗憾的是,由于我们采用的 12 GiB 对齐技巧,甚至连 8 GiB 的内存间隙都无法加以利用。如果我们向操作系统申请 12 GiB 内存,它永远不会给我们提供一个类似上图中绿色和蓝色沙箱之间那样的 8 GiB 间隙。此外,Linux 进程的虚拟地址空间中还存在大量其他活动:malloc 实现可能需要在特定地址获取内存页,可执行文件和库通过 ASLR(地址空间布局随机化)被映射到随机位置,而且 V8 还会在沙箱之外进行内存分配。
最新一代的 x64 CPU 支持更大的地址空间,这同时解决了两个问题;而 Linux 内核能够利用额外的地址位,通过五级页表实现。不过,进程必须主动启用此功能,只需通过一次 mmap 调用,申请一个超出 47 位地址范围的地址即可。之所以需要主动启用,是因为某些程序无法处理如此高的地址。有趣的是,V8 就是其中之一。
在 V8 中修复这个问题并不难,但我们的服务器集群尚未全部升级到所需的硬件。因此,目前我们需要一个能够兼容现有硬件的解决方案。我们修改了 V8,使其能够获取大容量内存区域,然后使用 mprotect 系统调用 为沙箱创建紧密封装的 8 GiB 空间,从而绕过不灵活的 mmap API。
图4
将上述内容结合在一起来看
像这样控制沙盒的位置实际上给我们带来了安全方面的好处,但首先我们需要阐述一个特定的威胁模型。
在本威胁模型的分析框架下,我们假设攻击者拥有任意篡改沙箱内数据的能力。这在历史上是众多 V8 漏洞利用案例中的初始步骤。这样的案例非常之多,以至于 Google V8 漏洞悬赏计划专门设立了特殊评级:只要您能合理假定自己具备这种内存篡改能力,并成功将其升级为更严重的漏洞利用,就能获得赏金。
不过,我们假设攻击者并不具备执行任意机器代码的能力。倘若他们真有这种能力,就能禁用内存保护密钥。因为攻击者即便能访问沙箱内的内存,也只能获取到他们自己的数据。所以,攻击者必须设法实施权限提升攻击,通过篡改沙箱内的数据来访问沙箱之外的数据。
大家应该还记得,经过压缩且处于沙箱环境中的 V8 堆内存仅包含 32 位偏移量。因此,即便此处发生数据损坏,也不会波及指针隔离笼之外的内存区域。不过,沙箱中还存在数组——这些是具有特定大小、可通过索引访问的数据集合。根据我们的威胁模型设定,攻击者能够篡改这些数组的记录大小以及用于访问数组元素的索引。这意味着,攻击者有可能将沙箱中的数组变成一种错误访问内存的工具。正因如此,V8 沙箱通常会在其周围设置保护区域:这些区域是 32 GiB 大小的虚拟地址范围,且没有虚拟到物理地址的映射。这有助于防范最坏的情况:使用最大 32 位索引对元素大小为 8 字节的数组(例如,双精度浮点数数组)进行索引。这样的访问可以到达沙箱外部最远 32 GiB 的范围:是最大 32 位索引(40 亿)的 8 倍。
我们希望此类访问操作能触发警报,而不是让攻击者得以访问邻近内存区域。使用保护区域时,这种防护效果会自动实现,但我们无法为每个沙箱都分配常规的 32 GiB 保护区域。
我们不必采用传统的保护区域方案,而是可以利用内存保护密钥。通过精细控制每个隔离组所使用的密钥,我们能够确保在 32 GiB 范围内,任意沙箱都不会使用相同的保护密钥。从本质上说,这些沙箱充当着彼此的保护区域,而这一保护机制正是由内存保护密钥来实现的。如此一来,我们只需要在大型连续沙箱区域的起始和末尾,各预留一段 32 GiB 的“浪费性”保护区域即可。
图5
采用全新的沙箱布局后,我们严格执行内存保护密钥的轮换机制。由于不再使用随机分配的内存保护密钥,在当前威胁模型下,前文所述的 “92%” 的问题得以彻底解决。沙箱内部的任何安全漏洞都无法影响到具有相同内存保护密钥的其他沙箱。如上图所示,任意沙箱周边 32 GiB 范围内都不存在共用相同内存保护密钥的内存区域——任何试图访问该范围内存的操作都将触发警报,其效果与未映射的保护区域完全一致。
未来规划
从某种程度上来说,本篇文章都是在谈论我们的用户“无需费心去做”的事情:他们无需升级服务器软件来获取最新补丁,我们已为他们完成;他们无需担心自己使用的配置是否最安全、最高效 - 我们已确保了这一点,他们仅需无忧地在 Workers 上编写代码即可。最后,如果您对这类工作感兴趣——尤其是如果您有 V8 或类似语言运行时的实现经验,那么十分欢迎您加入我们的团队。
Cloudflare 保护整个企业网络,帮助客户高效构建互联网规模的应用程序,加速任何网站或互联网应用程序,抵御 DDoS 攻击,防止黑客入侵,并能协助您实现 Zero Trust 的部署与实施。
从任何设备访问 1.1.1.1,使用我们的免费应用加速和保护您的互联网。
立即联系我们,获取更多相关信息http://t.cn/A6Tx4yTT
#Cloudflare##沙盒安全#
