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 封装:不直接在 FIDL 消息中传输大型对象,而是将它们复制到 VMO 中。通常,这表示为 fuchsia.mem/Buffer
    • 非常适合字节向量(blob)。
    • 对于结构化数据对象来说,这种方式比较麻烦,因为开发者需要负责调用序列化/反序列化。
    • 需要采取措施来降低因共享内存而导致的安全威胁。

未能预料到此问题是导致运行时不稳定性的主要原因。

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

设计

简要说明

盒子是可能需要带外传输的大型数据对象的容器,当总消息大小(包括标头)超过 Zircon 通道的限制时,可能需要带外传输1

箱子只能存放数据对象,不能存放具有句柄2的对象。

设计时,FIDL 协议作者..

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

编译时,FIDL 编译器...

  • 解析声明
  • 静态验证每个 box 是否仅包含数据对象(不包含句柄)
  • 静态验证每个 FIDL 消息的最大可能大小是否超出渠道限制(在静态消息大小强制执行模式下)
    • 假设向量和字符串的大小为其边界所允许的最大值
    • 假设可扩展的联合和结构(表)尽可能大
    • 拒绝递归结构,除非通过盒子打破递归

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

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

运行时,FIDL 编码器会..

  • 尽可能多地将已装箱的对象打包到消息正文中,并遵循所有其他带外对象
  • 打包所有未装入 VMO 的剩余 boxed 对象

运行时,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
    • 一个包含 boxed 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;
};

Wire Format

(此部分内容不完整。)

方案 1:在深度优先遍历序列化期间,将遇到的所有盒子添加到队列中;完成第一遍后,按顺序打包盒子中的物品(位于带外对象之后),直到没有剩余空间;然后计算剩余盒子对象的大小,分配单个 VMO,并从那里继续打包盒子内容

方案 2:与方案 1 类似,但将每个盒子都放入各自的 VMO 中,实现起来稍微简单一些,但可能更具限制性

想法 3:或许我们应该完全放弃方框的想法,改为在方法级别上做一些事情,例如添加注释,例如 [Huge]

绑定

(此部分内容不完整。)

增强型 VMO 系统调用

(此部分内容不完整。)

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

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

想法 3:如果 VMO 已经处于非共享状态,则真正检查是否要反向 COW 快照它(应该是无操作)

方案 4:放弃 VMO,改用视图

实施策略

(此部分内容不完整。)

工效学设计

(此部分内容不完整。)

文档和示例

(此部分内容不完整。)

向后兼容性

除了提供一种用于转移大型数据对象的机制之外,箱子还旨在解决静态安全性问题

目前,尝试传输超出渠道限制的 FIDL 消息的程序会在运行时失败,导致系统不稳定。一旦有了 box,就可以在 FIDL 编译器中引入静态消息大小强制执行机制,以便在编译时保证任何消息都不会超出通道限制;超出部分的内容可以由 FIDL 协议作者简单地移入 box 中。

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

我们建议通过以下方式解决此问题:

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

性能

(此部分内容不完整。)

安全

通过用 FIDL 语言绑定支持的官方解决方案替换现有的临时机制,我们有机会提高整体安全规范。

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

  • VMO 的提供方在客户端访问数据时修改数据。
  • VMO 的提供方在客户端访问 VMO 时更改 VMO 的大小,或者以其他方式诱使客户端发生页面错误。

相反,引入此功能可能会导致用户更多地使用大型消息,从而增加其他威胁(例如以下威胁)的可能性:

  • VMO 的提供方会向客户端发送巨大的回复,导致客户端在反序列化时分配大量堆。

测试

(此部分内容不完整。)

缺点、替代方案和未知因素

(此部分内容不完整。)

在先技术和参考资料

(此部分内容不完整。)

编辑者注:拒绝了 RFC-0062:方法不可能,该提案建议禁止所有可能超出协议限制的方法。由于静态分析的限制,这过于严格,无法捕获运行时行为来正确分页消息或手动“框定”消息。



  1. 从理论上讲,FIDL 可以通过其他渠道传输,而装箱可能具有不同的性质。如何实现这一点不在本提案的讨论范围内。 

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