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 上是否存在不可变性等限制,则可以更轻松地减小安全暴露面并验证正确性。

FIDL 中的大型消息支持

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

未来,FIDL 可能会提供对大型消息的支持。一种可能的处理方式是将消息封装为带外 VMO。包含 Box<Type> 的 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。

您可以通过多种方式将此信息传达给 Overnet:

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

遗憾的是,这些方法都有缺点,导致它们复杂且不如所提出的解决方案。

在先技术和参考资料

VMO 密封 已废弃的 RFC 提出了一种限制可对 VMO 执行的操作的机制。此功能可用于创建和测试不可变的 VMO,而无需进行克隆。 不过,与简单的不可变性检查相比,VMO 密封对 VMO 实现的侵入性要强得多。

最近,我们一直在开发 zx_vmar_map 的安全功能,例如 ZX_VM_ALLOW_FAULTS。未来可能需要向 zx_vmar_map 添加处理不可变性的功能。