RFC-0064:Box <Knox>

RFC-0064:盒子 <Knox>
状态已拒绝
领域
  • FIDL
说明

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

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

遭拒的理由

通过 FIDL 在对等设备之间传输大型消息这一问题非常重要,这是开发者当前面临的一个最新问题,目前还存在 FIDL 的不足之处。通道限制、易于定义的消息可超出这些限制(取决于运行时行为),再加上固有的消息大小调整(例如,最大分页)的组合,使得此问题在应用代码中难以解决(至少在大规模的情况下)。必须由 FIDL 提供解决方案。

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

此 RFC 包含许多可取的要素,将来会用作参考。具体而言:

  • 问题陈述和动机。
  • 倾向于使用静态验证来保证消息满足协议限制,或者选择启用绑定提供的运行时机制以“适应”(可能产生一些额外费用)
  • 通过区分“值类型与资源类型”,在便利性/性能连续性中实现不同的点。

尽管存在上述要求,但此 RFC 被拒,原因如下:

  • 消息的字节和句柄如何从一个对等端传输到另一个对等端不是传输格式问题,并且不应修改为满足相关要求而需要的传输格式。
  • 引入 box<T> 表示法有助于识别消息中可能发生装箱的位置,也就是说,它是一种精细的机制,用于指示消息中(几乎)任意位置的装箱行为。目前的思路是,最好在方法级别提供注解,即在使用网站(而非声明网站)上提供更粗略的说明机制。
  • 随着 FIDL 逐渐泛化以支持 Zircon 信道之外的不同传输,我们必须考虑到每种传输都有自己的限制。例如,当我们考虑将 FIDL 扩展为 Zircon fifos 时,大小限制将有所不同,并且不允许使用任何句柄。这进一步说明了最好使用方法级注解(而不是以类型为中心的注解)的视图。

目前,在应用中解决此问题的方法使用 fuchsia.mem/Data 类型,该类型是一个并集,代表内嵌数据或 VMO 中的数据。使用语法糖将此方法泛化到任何使用语法糖的请求和/或响应(如针对错误的操作)以及绑定支持,是当前有关此主题的主要竞争者。

总结

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

设计初衷

可通过 Zircon 通道在进程之间传输的 FIDL 消息的大小有上限,如 ZX_CHANNEL_MAX_MSG_BYTES 所定义。在写入数据时,上限为 64 KB(包括 FIDL 标头)。

虽然该限制足以满足许多应用的需求,但偶尔需要传输较大的对象。这给 FIDL API 设计人员带来了挑战,因为他们必须设计用于传输这些对象的替代方法,例如:

  • 分页:传输对象集合(通常为矢量)时,需批量传送对象,而不是一次性传送所有对象。
    • 只要每个对象都小于该限制,就可以正常运行(将标头和其他字段考虑在内)。
    • 对开发者来说,确定如何将对象高效打包到消息中而不超出预期限制有点困难,因为目前没有用于估算 FIDL 消息大小的 API 或用于增量构建 FIDL 消息大小的 API。
  • VMO 封装:不是在 FIDL 消息本身中传输大型对象,而是将它们复制到 VMO。通常表示为 fuchsia.mem/Buffer
    • 非常适合字节矢量 (blob)。
    • 这对于结构化数据对象来说会比较麻烦,因为开发者负责调用序列化/反序列化。
    • 需要遵守规则来缓解因共享内存而导致的安全威胁。

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

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

设计

内裤装

Box 用于存放可能较大的数据对象,当消息总大小(包括标头)超过 Zircon 通道1 的限制时,可能需要通过带外传输此对象。

box 只能保存数据对象,不能保存具有句柄2的对象。

设计时,FIDL 协议的作者。

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

在编译时,FIDL 编译器会...

  • 解析声明
  • 静态验证每个盒子是否仅包含数据对象(无标识名)
  • 静态验证每条 FIDL 消息的最大可能大小是否不超过通道限制(在静态消息大小强制执行模式下
    • 假定向量和字符串的大小要达到其允许的范围
    • 会假定可扩展的联合体和结构体(表)尽可能大
    • 拒绝递归结构,除非递归被一个框损坏

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

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

运行时,FIDL 编码器...

  • 将尽可能多的盒装对象打包到邮件正文中,然后跟随所有其他外行对象
  • 打包所有无法放入 VMO 的剩余盒装对象

运行时,FIDL 解码器...

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

语言详情

我们引入了一种新的内置 FIDL 类型,以 box<T> 表示。T 指定要放置在盒子中的对象的类型。

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

新类型 box<T> 可在任何可接受引用类型(例如 structsunionsvectorsstrings)时使用。

框类型示例

  • box<string>
    • 一个包含无界限字符串的框
  • box<int>
    • 错误:Box 不能包含基元类型的对象
  • 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

实施策略

(此部分不完整。)

工效学设计

(此部分不完整。)

文档和示例

(此部分不完整。)

向后兼容性

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

目前,尝试传输超过通道限制的 FIDL 消息的程序将在运行时失败,从而导致系统不稳定。Box 可用后,应该在 FIDL 编译器中强制引入静态消息大小,这样我们就可以保证在编译时不会有任何消息超过通道限制;FIDL 协议作者可以将排除的内容放入一个盒子中。

但是,一次性启用静态消息大小可能会破坏现有代码并阻碍迁移工作。

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

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

性能

(此部分不完整。)

安全性

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

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

  • VMO 的提供商会在客户端访问数据时修改数据。
  • 在客户端访问 VMO 时,VMO 的提供商会更改 VMO 的大小,或者导致客户端发生页面错误。

相反,引入此功能可能会导致大型消息的使用量增加,从而增加出现其他威胁的可能性,例如:

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

测试

(此部分不完整。)

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

(此部分不完整。)

早期技术和参考资料

(此部分不完整。)

编者注:已拒绝 RFC-0062: Method Im 不确定,该协议提出禁止超出协议限制的所有方法。由于静态分析的限制,这限制过于有限,会阻止捕获运行时行为以对消息进行正确分页,或手动“装箱”消息。



  1. 假设,FIDL 可通过其他信道进行传输,而这种信箱模式可能具有不同性质。具体实现方式不在本方案的讨论范围之内。 

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