RFC-0064:Box <Knox>

RFC-0064:Box <Knox>
状态已拒绝
区域
  • FIDL
说明

引入了一种机制,用于通过与 FIDL 消息关联的辅助 VMO 传输大型结构化数据对象。

作者
提交日期(年-月-日)2018-09-27
审核日期(年-月-日)2020-06-17

拒绝理由

在 FIDL 上跨对等方传输大型消息的问题非常重要,是开发者目前面临的一个重要问题,也是 FIDL 目前的缺陷。由于存在通道限制,并且可以轻松定义可根据运行时行为超出这些限制的消息,再加上消息大小调整固有的难度(例如最大限度分页),因此无法在应用代码中解决此问题(至少在规模上无法解决)。必须由 FIDL 提供解决方案。

简而言之,FIDL 需要为库作者提供一种方法,以消除因协议限制(例如 Zircon 通道中字节数和句柄数的硬限制)而导致运行时意外的可能性,并且 FIDL 开发者需要能够依赖于绑定来实现可能交换大型消息的协议。

此 RFC 包含许多有用的元素,日后将作为参考。具体而言:

  • 问题陈述和动机。
  • 优先使用静态验证来保证消息满足协议限制,或者选择启用绑定提供的运行时机制以“使其适用”(可能会产生一些额外费用)
  • 依靠值类型与资源类型之间的区别,在便捷性/性能权衡连续体中提供不同的点。

尽管如此,我们仍出于以下原因拒绝了此 RFC:

  • 消息的字节和句柄如何从一个对等方传输到另一个对等方不是线格格式问题,并且无需对线格格式进行任何修改即可满足要求。
  • 引入 box<T> 表示法有助于确定消息中可能发生装箱的位置,即它是一种精细机制,用于指示消息中(几乎)任意位置的装箱行为。相反,目前的想法是,最好在使用点(而非声明点)提供方法级注解,即使用更粗略的说明机制。
  • 随着 FIDL 逐渐泛化,以支持 Zircon 通道以外的其他传输,我们必须考虑到每种传输都有其自身的局限性。例如,我们希望将 FIDL 扩展到 Zircon fifo,因此大小限制将有所不同,并且不允许使用任何句柄。这进一步表明,方法级注解(而非以类型为中心的注解)更为理想。

目前,在应用中解决此问题的方法是使用 fuchsia.mem/Data 类型,这是一种联合体,用于表示内嵌或 VMO 中的数据。目前,最有可能推动此主题取得进展的方法是,使用语法糖(如同对错误所做的那样)将此方法推广到任何请求和/或响应,并提供绑定支持。

摘要

引入了一种机制,用于通过与 FIDL 消息关联的辅助 VMO 传输大型结构化数据对象。

设计初衷

可通过 Zircon 通道在进程之间传输的 FIDL 消息的大小上限由 ZX_CHANNEL_MAX_MSG_BYTES 定义。在撰写本文时,此限制为 64 KB,包括 FIDL 头文件。

虽然此限制对于许多应用来说已经足够,但有时您可能需要传输较大的对象。这对 FIDL API 设计者来说是一个挑战,因为他们必须设计用于传输这些对象的替代方法,例如:

  • 分页:传输对象集合(通常是矢量)时,以批量方式增量传送对象,而不是一次性传送所有对象。
    • 只要每个对象的大小都小于限制(包括标头和其他字段),就能够正常运行。
    • 由于目前没有用于估算 FIDL 消息大小或增量构建 FIDL 消息的 API,因此开发者很难确定如何高效地将对象打包到消息中而不超出限制。
  • VMO 封装:将大型对象复制到 VMO,而不是在 FIDL 消息本身中传输。通常,这表示为 fuchsia.mem/Buffer
    • 非常适合字节矢量(blob)。
    • 对于结构化数据对象,由于开发者负责调用序列化/反序列化,因此会很麻烦。
    • 需要严格遵守相关规定,以降低因共享内存而产生的安全威胁。

未能预见此问题是运行时不稳定的主要原因。

我们认为,FIDL 应提供内置安全静态可验证高效的机制来传输大型数据对象。

设计

简要说明框

1 框是可能较大的数据对象的容器,当消息总大小(包括标头)超出 Zircon 通道的限制时,可能需要通过非信道传输。

只能包含数据对象;它不能包含具有手柄的对象2

设计时,FIDL 协议作者...

  • 当预计包含这些对象的消息可能会超出信道限制时,将数据对象封装到框中

编译时间,FIDL 编译器...

  • 解析声明
  • 静态验证每个框中是否仅包含数据对象(无句柄)
  • 静态验证每个 FIDL 消息的可能最大大小是否不超过通道限制(在静态消息大小强制执行模式下
    • 假定矢量和字符串的大小不受边界限制
    • 假定可扩展的联合体和结构体(表)的大小可能达到最大值
    • 拒绝递归结构,除非递归被框架打断

编译时,每个 FIDL 代码生成器...

  • 生成足以序列化和反序列化封装数据的代码

运行时,FIDL 编码器...

  • 将尽可能多的封装对象打包到消息正文中,紧随所有其他线下对象
  • 打包所有无法放入 VMO 的剩余封装对象

运行时,FIDL 解码器...

  • 在访问包含封装对象的 VMO(如果有)之前,确保它拥有对 VMO 的唯一句柄(VMO 不共享)
  • 从邮件正文和 VMO 中提取封装的对象

语言详情

我们引入了一种新的内置 FIDL 类型,表示为 box<T>T 用于指定要放入框中的对象类型。

  • T 必须是直接或间接不包含句柄的引用类型。
  • T 不得是基元类型。
  • T 可以是可选类型。

新类型 box<T> 可在接受引用类型(例如结构体联合体矢量字符串)的任何位置使用。

示例盒子类型

  • box<string>
    • 包含无限字符串的框
  • box<int>
    • 错误:框不能包含基元类型的对象
  • box<vector<T>:100>
    • 一个包含 T 对象的边界矢量
  • box<vector<T>>
    • 一个包含 T 对象无限矢量的盒子
  • vector<box<T>>:100
    • 封装的 T 对象的边界矢量。
  • box<string:100>
    • 包含边界字符串的框
  • box<string>
    • 包含无限字符串的框
  • box<MyStruct>
    • 包含结构的框
  • box<MyStruct?>
    • 包含可选结构的框
  • box<MyStruct>?
    • 错误:复选框不能是可选的

声明示例

interface Database {
    // OK
    1: SelectTop(string:1000 query) -> (box<Record> record);

    // ERROR: reply may exceed message size limit
    // consider wrapping large objects in a box<>,
    // "Record" size is unbounded
    2: BadSelectTop(string:1000 query) -> (Record record);

    // OK
    3: SelectAll(string:1000 query) -> (box<vector<Record>> records);

    // ERROR: reply may exceed message size limit
    // consider wrapping large objects in a box<>,
    // "vector<Record>" size is unbounded
    4: BadSelectAll(string:1000 query) -> (vector<Record> records);
};

struct Record {
    string name;
    string address;
};

线格格式

(此部分未完成。)

方案 1:在进行深度优先遍历以进行序列化时,将遇到的所有盒子添加到队列中,完成第一个传递后,按线外对象的顺序打包盒装项,直到没有剩余空间,然后计算剩余盒装对象的大小,分配单个 VMO,并从中继续打包盒装内容

方案 2:与方案 1 类似,但将每个框放入自己的 VMO,实现起来稍微简单一些,但可能更受限制

方案 3:我们或许应该完全放弃使用框的想法,而是在方法级别执行一些操作,例如注解,例如 [Huge]

绑定

(此部分未完成。)

增强型 VMO 系统调用

(此部分未完成。)

方案 1:定义“确保未共享”标志,验证 VMO 只有一个句柄,与其他 VMO 没有共享任何页面,并且未映射,可以将此标志传递给 zx_vmo_read/write/map 等。

方案 2:定义一个新的系统调用来检查 VMO 是否未共享

方案 3:让其真正检查是否已取消共享 VMO,如果已取消共享,则执行反向 COW 快照(应为无操作)

方案 4:放弃使用 VMO,改用 View

实施策略

(此部分未完成。)

工效学设计

(此部分未完成。)

文档和示例

(此部分未完成。)

向后兼容性

除了提供用于传输大型数据对象的机制之外,盒子还旨在解决静态安全问题

目前,如果程序尝试传输超出通道限制的 FIDL 消息,则会在运行时失败,导致系统不稳定。箱子可用后,我们应该能够在 FIDL 编译器中引入静态消息大小强制执行,以便在编译时保证任何消息都不会超出信道限制;超出的内容可以由 FIDL 协议作者直接移至箱子中。

不过,如果同时启用静态消息大小强制执行,可能会破坏现有代码并阻碍迁移工作。

我们建议按如下方式解决此问题:

  • 最初,请在宽容模式下启用静态消息大小检查。
    • FIDL 编译器应检查消息大小,并在可能超出通道限制时发出警告。建议 FIDL 协议作者改为使用框。
  • 继续迁移。
  • 完成后,在强制执行模式下启用静态消息大小检查。
    • FIDL 编译器应检查消息大小,并在可能超出通道限制时发出错误。停止编译。

性能

(此部分未完成。)

安全

通过将现有的临时机制替换为由 FIDL 语言绑定支持的官方解决方案,我们有机会改进整体安全纪律。

例如,FIDL 语言绑定可确保在尝试访问其内容之前,包含封装数据的 VMO 只有单个所有者。这可解决常见的共享内存威胁,例如:

  • VMO 的提供程序会在客户端访问数据时修改数据。
  • VMO 的提供程序在客户端访问 VMO 时更改 VMO 的大小,或以其他方式诱导客户端发生页面故障。

反之,引入此功能可能会导致用户增加使用大型消息的频率,从而增加其他威胁的可能性,例如:

  • VMO 的提供程序向客户端发送巨大的响应,导致客户端在反序列化时分配大量堆。

测试

(此部分未完成。)

缺点、替代方案和未知

(此部分未完成。)

在先技术和参考文档

(此部分未完成。)

编辑者备注:拒绝了 RFC-0062:Method Impossible,该提案建议禁止所有可能超出协议限制的方法。由于静态分析的局限性,这种方法过于限制,无法捕获运行时行为以正确分页消息,也无法手动“封装”消息。



  1. 假设 FIDL 可以通过其他渠道传输,对于这些渠道,封装可能具有不同的性质。此提案不涉及实现方式。 

  2. 编者注释:由于此 RFC 是在 2018 年撰写的,因此值类型和资源之间的区别正式成为 FIDL 语言的一部分,请参阅 RFC-0057。