RFC-0080:检测 VMO 不可变性

RFC-0080:检测 VMO 不可变性
状态已接受
涉及的领域
  • Kernel
说明

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

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

摘要

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

设计初衷

添加用于检查不可变性的机制主要有三个设计初衷。

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

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

如果可以测试 VMO 的不可变性等限制,则可以更轻松地减少安全隐患并验证正确性。

FIDL 中的大型消息支持

我们明确选择将通道消息限制为 64k,并让需要更大尺寸的应用自行寻找解决方法。

将来,FIDL 可能会提供对大型消息的支持。一种可能的处理方法是将消息装箱为带外 VMO。 包含 Box<Type> 的 FIDL 定义会让 FIDL 自动将消息的相应部分复制到 VMO。

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

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

Overnet

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

Overnet 图

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

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

从应用的角度查看

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

在单个机器中,VMO 按引用传递,无论它们是可变的还是不可变的,也就是说,在通道中发送 VMO 不会导致 Zircon 隐式复制它。这种实现方式简单高效。 但是,通过网络在多个机器之间按引用传递效率低下且不切实际。相反,按复制传递适合这些场景。

挑战在于,按复制传递并非总是安全可靠。由于 Overnet 是一种透明代理,因此远程进程希望对同一 VMO 的引用保持同步。按复制传递会更改 VMO 的语义,并可能以细微的方式破坏应用。例如,如果一个假设的媒体播放器进程向远程扬声器进程发送 VMO,然后向 VMO 写入歌曲,则扬声器将永远不会收到歌曲或无法播放歌曲,因为歌曲在首次发送时不在 VMO 中。

幸运的是,在某些情况下,按复制传递是安全的。其中一种情况是,到达 Overnet 代理的 VMO 是不可变的。应用已放弃更改数据的能力,因此可以保证对数据进行的任何复制都将保持一致。

如果 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”。如果 Overnet 能够判断给定的 VMO 是否具有此属性,则可以决定是否发送 VMO 或拒绝 VMO。

有多种方法可以将此信息传达给 Overnet:

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

遗憾的是,这些方法都有缺点,使得它们复杂且不如建议的解决方案。

在先技术和参考资料

VMO Sealing Abandoned RFC 提出了一种机制来限制可以对 VMO 执行的操作。这可以用来代替克隆来创建和测试不可变的 VMO。 但是,与简单的不可变性检查相比,VMO Sealing 对 VMO 实现的侵入性要强得多。

最近,我们一直在研究 zx_vmar_map 的安全功能,例如 ZX_VM_ALLOW_FAULTS。将来,我们可能希望向 zx_vmar_map 添加处理不可变性的功能。