RFC-0080:检测 VMO 不可变性

RFC-0080:检测 VMO 不可变性
状态已接受
领域
  • 内核
说明

此 RFC 提出了新标记“ZX_INFO_VMO_IMMUTABLE”,可用于确定 VMO 是否不可变,以及自创建以来是否一直存在。

作者
审核人
提交日期(年-月-日)2021-02-18
审核日期(年-月-日)2021-03-29

总结

此 RFC 提出了新标志 ZX_INFO_VMO_IMMUTABLE,该标志可以确定 VMO 是否不可变,以及是否自创建以来一直存在。

设计初衷

添加用于检查不可变性的机制有三个主要动机。

提高使用共享内存的安全性

很难编写安全且正确的使用共享内存的程序。某些程序不需要完全可变的 VMO,它们可能只向 VMO 写入一次,然后只从 VMO 中读取。

如果可以在 VMO 上测试不可变性等限制,则更容易减少安全表面并验证正确性。

FIDL 中的大型消息支持

明确选择将通道消息限制为 64k,由需要较大大小的应用负责寻找解决方法。

FIDL 未来可能会支持大型消息。一种可能解决此问题的方法是将消息装箱作为离线 VMO。对于包含 Box<Type> 的 FIDL 定义,FIDL 会自动将该部分消息复制到 VMO。

另一种处理方法是在 VMO 中自动发送超过 64k 的所有 FIDL 消息。这是已撤消的 RFC 中采用的方法。

在这两种情况下,消息的接收者都无法验证消息在读取时是否会发生变化。如果有某种方法可以确认不会修改 VMO,则更容易验证 VMO 是否被安全地使用,并且还可能带来优化机会。

过网

Overnet 是一种通过网络传输消息的通道级代理。

过网图

Overnet 是一种透明代理。Overnet 客户端发送通道消息的方式与发送本地进程的方式相同。然后,这些消息由 Overnet 序列化,通过网络发送并在另一端进行重建。接收进程接收消息的方式与接收任何其他通道消息相同。

使用 Overnet 的应用将看到以下内容:

从应用的角度查看

Overnet 目前不支持发送包含 VMO 的消息。对于多个 Overnet 客户端来说,这已成为一个痛点,这些客户端需要发送超过现有 64k 通道限制的消息,否则会在 VMO(封装在 fuchsia.mem.Buffer 或 fuchsia.mem.Data FIDL 类型中)发送大量数据块。

在单台机器中,无论 VMO 是否可变,均通过引用进行传递;也就是说,在通道中发送 VMO 不会导致 Zircon 隐式复制它。该实现过程简单高效。 但是,通过网络在多台机器之间通过引用进行传递是低效和不切实际的。相反,按复制传递适合这些场景。

难点在于,按文案传递并不总是安全的。由于 Overnet 是透明代理,因此远程进程希望对同一 VMO 的引用保持同步。传递复制会改变 VMO 的语义,并且可能会轻微破坏应用。例如,如果某个假设的媒体播放器进程将 VMO 发送到远程扬声器进程,然后将歌曲写入 VMO,那么扬声器将永远不会收到歌曲,也无法播放,因为当歌曲首次发送时,VMO 中并不存在这首歌。

幸运的是,在某些情况下,按副本传递是安全的。一种情况是,到达 Overnet 代理的虚拟机 O 是不可变的。应用已经放弃更改数据的能力,因此可以保证由数据构成的所有副本都将保持一致。

如果 Overnet 能够检测到不可变的 VMO,那么它在遇到可变 VMO 时可能会快速失败。这样做可以避免会导致应用静默中断的细微 bug。

设计

zx_info_vmo_tflags 字段中添加新的 ZX_INFO_VMO_IMMUTABLE 标志,指示 VMO 是否不可变,能否得到保证和验证。

目前,如果 VMO 是使用 zx_vmo_create_child 系统调用创建的,并且设置了 ZX_VMO_CHILD_SNAPSHOTZX_VMO_CHILD_NO_WRITE 标志且未设置 ZX_VMO_RESIZABLEZX_VMO_DISCARDABLE 标志,则该 VMO 是不可变的。但是,如果引入创建不可变 VMO 的新方法,用于确定 VMO 是否不可变的公式未来可能会发生变化。

确定何时使 VMO 不可变由应用来完成。

实现

实现相对简单。计算 ZX_INFO_VMO_IMMUTABLE 的值所需的大部分状态已存储在内核中。

这条规则有一种例外情况,那就是表示 VMO 在创建时不可写入。此文件可存储在 VmObjectDispatcher 中。

性能

添加此标志不会对性能产生直接影响。

但是,创建不可变 VMO 目前的费用不高,因此应仅在绝对必要时才使用。对于 Overnet,不可变克隆的相对开销可能不太明显,因为 Overnet 已经执行了大量发送消息来发送消息,包括网络通信。

运行并比较了两项基准,以进一步了解不可变克隆 (CL) 的费用:

  • 创建 VMO、写入、读取、关闭
  • 创建 VMO、写入、不可变克隆、读取克隆 VMO、关闭

需要强调的是,此基准可反映当前的性能,但克隆存在优化空间,特别是对于不可变克隆情况。

此基准是使用 Intel NUC NUC7i5DNHE 记录的。

快照克隆的影响

在 64 KiB 之前,执行不可变克隆需要 2 美元的费用,之后克隆会开始增加。超出 2 MiB 后,费用会显著增加。

但值得注意的是,与整体创建-写入-克隆-读取-销毁流程相比,克隆的相对费用会随着大小的增加而降低:

快照克隆的相对影响

对于 64k VMO,不可变克隆会增加 20% 的时间,这是相当明显的开销,但随着大小的增加,开销会逐渐减少到零。

安全注意事项

通过此方法验证声明为不可变的 VMO 是否确实不可变非常重要。如果 VMO 被错误地标识为不可变,则会导致安全 bug。

隐私注意事项

无影响。

测试

标记实现将通过单元测试进行验证。

文档

目前,ZX_INFO_VMO_* 标志中缺少文档,读取器称为代码库。作为此更改的一部分,将编写文档来描述 ZX_INFO_VMO_* 选项。

缺点、替代方案和未知情况

针对不同属性使用多个标志来确定不可变性

此 RFC 提议使用单个 ZX_INFO_VMO_IMMUTABLE 标志来确定不可变性。而是可以在用户端使用几个单独的标志来确定这一点:

  • ZX_INFO_VMO_IS_CHILD_SNAPSHOT
  • ZX_INFO_VMO_IS_INITIALLY_WRITABLE
  • ZX_INFO_VMO_IS_RESIZABLE
  • ZX_INFO_VMO_IS_DISCARDABLE

基于底层状态使用多个标志的好处是,不需要专用标志来实现不可变性,并且只公开可以在不同组合中使用的基本属性。

但是,通过这些标志确定不可变性相对比较复杂,并且不可变性本身可视为基本属性。通过为不可变性设置专用标志,可以更轻松地避免安全 bug。此外,单个标志使您可以更轻松地引入创建不可变 VMO 的新方法。

检查最后一个剩余引用

您可能会想要使用 ZX_INFO_HANDLE_COUNT 来检查进程是否持有某个 VMO 的最后一个句柄,并假定如果拥有该句柄,则没有其他进程可以改变该 VMO。不过,这种假设是不正确的,因为句柄只是 Zircon 中的一种引用类型。例如,VMO 的内存映射对 ZX_INFO_HANDLE_COUNT 值没有影响,因此进程无法使用此值来确定它是否对 VMO 具有独占访问权限。

通过 FIDL 将安全状况传达给 Overnet

可以对 FIDL 语言进行更改,以便将 VMO 标记为不可变或“可溢出”。如果 Overnet 能够判断给定的 VMO 是否具有该属性,则可以决定是发送还是拒绝 VMO。

将此信息传达给 Overnet 的方式有多种:

  • 将其放入句柄上的未使用的位中
  • 添加了一种机制,用于在 VMO 上存储 Overnet 可读取的位
  • 在 FIDL 消息标头中预留 64 位,以将 VMO 句柄标记为不可变
  • 让 Overnet 通过协议与客户端通信

遗憾的是,这些都存在缺点,使其很复杂,因此不适合我们提出的解决方案。

早期技术和参考资料

VMO Sealing Abandoned RFC 提出了一种机制,以限制可对 VMO 执行的操作。可以使用这种方式(而不是克隆)来创建和测试不可变的 VMO。但是,与简单的不可变性检查相比,VMO 密封对 VMO 实现的影响更大。

最近,我们在 zx_vmar_map 的安全功能方面做出了一些改进,例如 ZX_VM_ALLOW_FAULTS。未来,可能需要将处理不可变性的功能添加到 zx_vmar_map