Spectre 和 Meltdown 对 WebKit 意味着什么

安全研究人员最近发现了被称为 Meltdown(熔断)Spectre(幽灵)的安全问题。这些问题影响所有现代处理器,允许攻击者获取对原本应保密的内存区域的读取权限。要发起基于 Spectre 或 Meltdown 的攻击,攻击者必须能够在受害者的处理器上运行代码。WebKit 受到了影响,因为为了渲染现代网页,任何网页 JavaScript 引擎都必须允许不可信的 JavaScript 代码在用户处理器上运行。Spectre 直接影响 WebKit。Meltdown 对 WebKit 造成影响是因为在 WebKit 被用于发起 Meltdown 攻击之前,必须首先绕过 WebKit 的安全属性(通过 Spectre)。

  • WebKit 依靠 分支指令 来强制约束不可信的 JavaScript 和 WebAssembly 代码的行为。Spectre 意味着攻击者可以控制分支,因此仅凭分支已不足以强制执行安全属性。

  • Meltdown 意味着用户态代码,例如在网页浏览器中运行的 JavaScript,可以读取内核内存。并非所有 CPU 都受到 Meltdown 的影响,并且 Meltdown 正在通过操作系统变更得到缓解。通过在 WebKit 中运行 JavaScript 发起 Meltdown 攻击需要首先绕过基于分支的安全检查,就像 Spectre 攻击那样。因此,修复分支问题的 Spectre 缓解措施也能阻止攻击者将 WebKit 作为 Meltdown 的起点。

本文档解释了 Spectre 和 Meltdown 如何影响现有的 WebKit 安全机制,以及 WebKit 正在部署哪些短期和长期修复来提供针对这类新型攻击的保护。首批缓解措施已于 2018年1月8日 发布。

  • iOS 11.2.2.
  • High Sierra 10.13.2 补充更新。此更新沿用了 10.13.2 版本号。您可以通过查看 Safari 中的“关于 Safari”来验证您的 Safari 和 WebKit 是否已打补丁。版本号应为 13604.4.7.1.6 或 13604.4.7.10.6。
  • Safari 11.0.2 (适用于 El Capitan 和 Sierra)。此更新沿用了 11.0.2 版本号。已打补丁的版本号为 11604.4.7.1.6 (El Capitan) 和 12604.4.7.1.6 (Sierra)。

Spectre 与安全检查

Spectre 意味着分支不足以强制执行 WebKit 中读取操作的安全属性。受影响最大的子系统是 JavaScriptCore (WebKit 的 JavaScript 引擎)。几乎所有的边界检查都可以被绕过,从而实现任意越界读取。这可能允许攻击者读取任意内存。所有类型检查也都是脆弱的。例如,如果某种类型在偏移量 8 处包含一个整数,而另一种类型在偏移量 8 处包含一个指针,那么攻击者可以使用 Spectre 绕过本应确保您无法使用该整数来构造任意指针的类型检查。

JavaScriptCore 的设计初衷是一个安全的语言虚拟机。应该可以在不将进程内存泄露给 JavaScript 代码的风险下,将不可信的 JavaScript 或 WebAssembly 代码加载到您的进程中,除非您通过我们的 C 或 Objective-C 绑定 API 明确地将数据导出到 JavaScript。Spectre 打破了 JavaScriptCore 的这一特性,因为不可信的 JavaScript 或 WebAssembly 现在在理论上拥有了读取主机进程全部地址空间的路径。

DOM API 以及由 DOM API 调用的系统 API 也使用分支来强制执行其安全属性,并且这些 API 可以从 JavaScript 调用。因此,Spectre 不仅是对 JavaScriptCore 本身的攻击,也是对所有可从 JavaScript 调用的东西的攻击。

理解 Spectre

为了理解 Spectre 如何工作,有必要思考安全性敏感的编程语言操作(例如 JavaScript 中的任何属性访问)如何在现代处理器上于 JavaScriptCore 中执行。大多数关于 Spectre 的讨论都涉及边界检查,因此本节也将考虑这种情况。

var tmp = intArray[index];

在此示例中,假设我们的 JavaScript 引擎知道 intArray 是一个 Int32Array 实例的引用,但我们尚未证明 indexintArray.length 的范围内。当编译器将这个高级 JavaScript 操作转换为低级形式时,必须发出一个边界检查。

if (((unsigned) index) >= ((unsigned) intArray->length))
    fail;
int tmp = intArray->vector[index];

在 x86 CPU 上编译此代码会产生以下指令

mov 0x10(%rsi), %rdx     ; %rsi has intArray. This loads
                         ; intArray->vector.
cmp 0x18(%rsi), %ecx     ; %ecx has the index. This compares
                         ; the index to intArray->length.
jae Lfail                ; Branch to Lfail if
                         ; index >= intArray->length according
                         ; to unsigned comparison.
mov (%rdx,%rcx,4), %ecx  ; Load intArray->vector[index].

现代 CPU 能够并行执行边界检查分支 (jae) 和随后的加载 (mov (%rdx,%rcx,4), %ecx)。这是因为

  • 现代 CPU 会对分支进行画像。在这种情况下,它们会观察到分支总是“落空”(不跳转)。这称为 分支预测
  • 现代 CPU 可以回滚执行。分支之后的加载可以在 CPU 验证该分支实际上是否“落空”之前执行。如果结果表明分支被“跳过”了,CPU 可以撤销从遇到分支到最终验证分支之间发生的一切。这称为 推测执行

Spectre 是一种利用推测执行中的信息泄露的攻击。考虑以下代码

var tmp = intArray[index];
otherArray[(tmp & 1) * 128];

CPU 在推测执行期间有能力将主内存中的数据加载到 L1(CPU 的一级缓存,最快且最小)。作为一种性能优化,CPU 在回滚推测执行时不会撤销对 L1 的读取。这导致了基于时间的信息泄露:在这段代码中,CPU 加载 otherArray[0] 还是 otherArray[128] 取决于 tmp & 1,并且可以通过测量对 otherArray[0]otherArray[128] 的访问速度来确定 CPU 推测性地加载了哪一个。

到目前为止的示例涉及控制边界检查分支。这是一种特别有效的 Spectre 攻击,因为 index 的行为类似于一个指针,可以用来读取 intArray->vector 以上约 16GB 的内存。但是 Spectre 理论上可能涉及任何强制执行安全属性的分支,例如 JavaScriptCore 中用于类型检查的分支。

总结

  1. Spectre 需要高精度定时,以便观察 L1 延迟和主内存延迟之间的差异。
  2. Spectre 允许攻击者控制分支。推测执行根据过去的执行历史执行分支,而攻击者可以控制这段历史。因此,攻击者控制了分支在推测执行期间的行为。
  3. Spectre 是分支验证器和信息泄露加载(我们在上面写为 otherArray[(tmp & 1) * 128])启动之间的一场竞速。攻击者知道他们是否赢得了这场竞速(otherArray 的两条缓存行之一将进入 L1),因此只要攻击者获胜的机会不为零,攻击就会奏效。

缓解 Spectre

WebKit 针对 Spectre 的应对是两层防御

  1. WebKit 已禁用 SharedArrayBuffer 并降低了计时器精度。
  2. WebKit 正在从基于分支的安全检查过渡到使用无分支安全检查作为补充。

其中一些变更已包含在 1月8日的更新中,并且更多此类变更正在陆续进入 WebKit。本文档的其余部分将详细介绍我们的缓解措施。

降低计时器精度

我们正在降低 WebKit 中的计时器精度。这些变更已在 r226495 合并到主干,并已包含在 1月8日的更新中发布。

  • 来自 performance.now 和其他源的计时器精度降低到 1ms (r226495)。
  • 我们已禁用 SharedArrayBuffer,因为它可用于创建高分辨率计时器 (r226386)。

我们的长期计划是即使在高精度计时存在的情况下也使 Spectre 攻击不可能发生;要重新启用此路径,还需要进一步的工作。

无分支安全检查

自了解 Spectre 以来,我们一直在研究如何在不依赖分支的情况下进行安全检查。我们已经开始向这种新的安全检查方式过渡,并在 1月8日的更新中发布了首批无分支检查。

索引屏蔽

无分支安全检查最简单的例子是屏蔽数组访问的索引

int tmp = intArray->vector[index & intArray->mask];

现代 CPU 不会对位屏蔽进行推测。如果选择合适的屏蔽值以适应数组长度,此缓解措施可确保即使存在 Spectre,攻击者也无法读取数组范围之外的数据。

我们已为以下内容实现了索引屏蔽

  1. 类型化数组 (r226461),
  2. WebAssembly 内存 (r226461,主要通过与类型化数组共享代码实现),
  3. 字符串 (r226068),
  4. WTF::Vector (r226068),以及
  5. 普通 JavaScript 数组 (r225913)。

变更 (1-4) 已在 1月8日的更新中发布。(5) 已进入 WebKit 但尚未发布。

索引屏蔽尚未完全解决越界访问问题。我们当前的索引屏蔽缓解措施使用的屏蔽值是通过将长度向上舍入到下一个二的幂(然后减一)计算得出的。这仍然允许越界读取,只是不能读取任意内存。

我们目前的测试表明,索引屏蔽对 Speedometer 和 ARES-6 测试没有可衡量的影响,对 JetStream 基准测试的影响小于 2.5%。

指针“中毒”

索引屏蔽易于应用于数组访问,但许多安全检查分支与对象类型有关,而非数组边界。指针“中毒”是一种通过改变被检查对象的形状来使任何类型检查在 Spectre 下安全的技术。

指针“中毒”仅意味着对其进行一些可逆的数学运算,以使访问尝试失败,除非该指针“解毒”。在 WebKit 中,“中毒”涉及与一个值进行异或运算,该值在高位至少设置一位,以便未“解毒”的访问很可能命中未映射的内存。WebKit 使用具有许多微妙规则的编译时随机数生成器选择“中毒”值,但要理解这种方法,只需将 1 << 40 视为一个“中毒”值。在一个有效指针上增加一兆字节,在 macOS 和 iOS 上的 WebKit 内存布局中必然会产生一个未映射的指针,可能在其他操作系统上也是如此。许多可能的“中毒”值都可以产生这种效果。

当指针字段的每个静态声明都有一个独特的“中毒”值,并且这些“中毒”值在高位不同,从而使用错误的值“解毒”会导致未映射指针时,“中毒”变得最为强大。

作为将指针“中毒”应用于无分支类型检查的一个例子,考虑一个类 Foo,它属于一个基于某些检查参与动态向下转换的类层次结构

class Foo : public Base {
public:
    ...
private:
    int m_x;
    Bar* m_y;
};

为了使此类在 Spectre 下的类型检查健全,我们可以简单地将 Foo 的字段移出到一个由唯一“中毒”指针指向的数据结构中

class Foo : public Base {
public:
    ....
private:
    struct Data {
        int x;
        Bar* y;
    };
    ConstExprPoisoned<FooDataKey, Data> m_data;
};

其中 FooDataKeyFoo 独有的键,基于该键,ConstExprPoisoned<> 将在编译时计算一个随机的“中毒”值。如果此层次结构中的所有类都使用指针“中毒”间接访问来保护其字段,则这足以作为对此类所有访问的无分支类型检查。除了是极好的 Spectre 缓解措施外,这还是一种有用的远程代码执行缓解措施,因为它使得进行任何类型的类型混淆变得更加困难。

指针“中毒”有时不需要任何额外的间接访问。JavaScriptCore 有大量大致符合这种模式的数据结构

struct Thingy {
    Type type;
    void* data; // The shape of this depends on `type`.
};

任何此类数据结构的类型检查都可以通过根据依赖于 type 的“中毒”值来“中毒” data 指针而实现无分支。

我们已开始将 WebKit 的对象模型转换为使用指针“中毒” (r225363, rr225437, r225632, r225659, r225697, r225857, r226015, r226247, r226344, r226485, r226530),并且部分初始指针“中毒”工作已包含在 1月8日的更新中发布(具体为 r225363r225857)。到目前为止,我们尚未观察到使用指针“中毒”带来的任何性能下降。

缓解 Meltdown

Meltdown 允许用户态代码读取内核内存。WebKit 间接受到 Meltdown 的影响,因为可以使用任何类型的代码执行(包括 JavaScript)发起 Meltdown 攻击。然而,用于执行 Meltdown 的内存访问将违反 WebKit 的安全模型。因此,基于 JavaScript 的 Meltdown 攻击必须首先绕过 WebKit 的安全检查,例如通过使用 Spectre 攻击。

如果 Meltdown 已通过操作系统变更得到缓解,那么即使 WebKit 没有任何 Spectre 缓解措施,也无法通过 WebKit 发起 Meltdown 攻击。任何未来的 Spectre 缓解措施都将进一步降低 WebKit 被用于 Meltdown 攻击的可能性,因为这种攻击的 Spectre 阶段将更加困难。

针对应用开发者的建议

Spectre 意味着与不可信 JavaScript 位于同一地址空间中的秘密比以往任何时候都更加脆弱。基于此,我们建议

  • 如果您尚未这样做,请切换到现代 WebKit API。这可以通过在另一个进程中运行不可信 JavaScript 来保护您的应用。
  • 避免将高精度计时源暴露给不可信的 JavaScript 或 WebAssembly。

结论

Spectre 和 Meltdown 是一类影响现代处理器及其上运行软件的新型安全问题。WebKit 受到这两个问题的影响,因为 WebKit 允许不可信代码在用户的处理器上运行。为了应对这些新问题,我们已经实施了缓解措施来防御 Spectre(以及通过浏览器使用 Spectre 发起的 Meltdown 攻击)。首批缓解措施已包含在 1月8日的更新中发布(iOS 11.2.2、High Sierra 10.13.2 补充更新以及 Safari 11.0.2 重新发布)。请继续关注更多 WebKit Spectre 修复!