| RFC-0034:以 null 结尾的字符串 | |
|---|---|
| 状态 | 已拒绝 |
| 区域 |
|
| 说明 | 我们建议 FIDL 中的字符串以 null 结尾(同时保留字符串的大小,与目前的情况一样)。此外,更改了 C 绑定以使用 uint8_t* 来表示字符串数据 |
| 作者 | |
| 提交日期(年-月-日) | 2019-02-08 |
| 审核日期(年-月-日) | 2019-02-21 |
拒绝理由
摘要
我们建议:
FIDL 中的字符串将以 null 结尾(同时保留字符串的大小,与目前的情况一样);
更改 C 绑定以使用
uint8_t*处理字符串数据。
设计初衷
当前的 FIDL 字符串编码很容易导致意外编写出不安全的代码。C 和低级 C++ 绑定用于 Fuchsia 系统中一些最受限的代码,因此应格外重视这些层级中暴露的风险。
我们曾意外发现此类 bug,但对 Fuchsia 树中的代码进行系统性审核非常困难,并且无法防止它们再次出现在我们的树中或第三方代码中。
设计
此提案建议更改 FIDL 字符串的编码,以包含一个值为零的终止字节。这不会使 FIDL 字符串与 C 字符串兼容,但如果将 FIDL 字符串用作 C 字符串,则它会被解读为比预期短,而不是比预期长,这更安全。
Wire Format
用于有线编码的字符串的新定义如下(更改已突出显示):
- 表示文本的可变长度 UTF-8 编码字符序列。
- 可为 null;null 字符串和空字符串是不同的。
- 可以指定最大大小,例如
string:40表示最大 40 字节的字符串(不包括 null 终止符)。 - 字符串内容包含 null 终止符。
- 编码器和解码器必须验证字符串的最后一个字节后是否存在空字节(如长度所示)。
- 存储为由以下内容组成的 16 字节记录:
size:64 位无符号代码单元(字节)数,不包括 null 终止符data:64 位存在指示或指向带外字符串数据的指针
- 在编码以进行传输时,
data表示存在内容:0:字符串为 nullUINTPTR_MAX:字符串不为 null,数据是下一个带外对象
- 解码以供使用时,
data是指向内容的指针:0:字符串为 null<valid pointer>:字符串不为 null,data位于指示的内存地址
字符串的表示法如下:
string:不可为 null 的字符串(如果遇到 null 数据,则会发生验证错误)string?:可为 null 的字符串string:N、string:N?:长度不超过 N 个代码单元的字符串
这将构成破坏性的线格式更改,具体来说,长度可被 8 整除的字符串将长 8 个字节(它将与 8 字节对齐,并以 null 结尾,导致再添加 7 个字节作为填充以再次与 8 对齐)。
需要更新编码器,以向编码后的字符串添加 null 终止符,并验证字符串内容中是否没有 null 字符。需要更新解码器,以检查字符串内容中是否没有 null 字符,但长度指示的位置是否有 null 终止符。
C 绑定
目前,C 绑定将字符串表示为 char* 和 size_t。如果将该 char* 传递给需要 C 字符串的函数,则可能会被错误地解释。绑定将更改为使用 uint8_t*,以便将字符串数据指针传递给 strchr() 或 printf("%s") 时无法编译。
实施策略
这是一项重大的线格式变更。需要在所有 FIDL 用例中仔细协调其部署。
在某些 build-time 标志后面,需要更新以下代码:
//zircon/system/ulib/fidl/walker.h(以正确验证字符串)//zircon/system/host/fidl/lib/flat_ast.cpp(更新StringType::Shape)//zircon/system/host/fidl/lib/c_generator.cpp(更新EmitLinerarizeMessage、ProduceInterfaceClientImplementation等)//sdk/lib/fidl/hlcpp/string.cc//garnet/public/lib/fidl/rust/fidl/src/encoding.rs//third_party/go/src/syscall/zx/fidl/encoding.go(更新marshalString,unmarshalString)//third_party/go/src/syscall/zx/fidl/encoding_new.go(更新mString)//sdk/dart/fidl/lib/src/types.dart(更新StringType)- llcpp 绑定
- 其他语言的树外绑定
应使用 fidl_compatibility_test 对这些方面进行测试,运行测试并确认系统稳定性是否符合预期。
实际提交更改需要与发布团队和 Chromium 等外部团队协调。
工效学设计
这使得 C 和低级 C++ 绑定更容易正确使用,并且不会影响其他绑定。
文档和示例
应更新有线格式文档(如上所述)。
向后兼容性
此变更在 API 方面兼容,但在 ABI 方面不兼容。
性能
这不会影响 build 性能,但会产生以下轻微的性能影响:
- 每个字符串平均需要额外传输一个字节
安全
此变更专门用于修复使用 FIDL 的内存不安全语言的潜在安全漏洞。
测试
此功能将使用兼容性测试套件进行测试。应扩展该套件,以确保正确处理长度为 7、8 和 9 字节的字符串等极端情况。
缺点、替代方案和未知因素
这会使 FIDL 消息的大小平均每个字符串增加一个字节。 对于长度不能被 8 整除的字符串,不会有任何变化。对于长度可被 8 整除的字符串,长度将增加 8 个字节。
替代方案
取消应用启动
我们可以保持一切不变,并依靠代码审核和文档来确保人们不会编写错误的代码。采用新的低级 C++ 绑定后,此问题也会得到一定程度的缓解。此类由细微问题引起的 bug 可能会造成严重危险,因此不能放任不管。
以 null 结尾,但禁止嵌入 null 字节 / 使用修改后的 UTF-8
(这是最初的提案)
'\0' 是有效的 UTF-8 字符,存在于 FIDL 用户想要交换的 UTF-8 字符串中。禁止使用 null 字节会使 FIDL 不适合许多用途,而使用修改后的 UTF-8 会增加将可能包含 null 字节的常规 UTF-8 转换为修改后的 UTF-8 的额外开销。
只需使用 uint8_t 而非 char
如果 FIDL 字符串数据指针是 uint8_t 而不是 char,那么在不进行转换的情况下,它们无法传递给标准字符串函数或 printf。这有助于发现此类 bug,但无法防止它们。
在调试 build 中使用有效的 ASCII 填充内边距
八个字符串中有七个后面会跟零填充字节。在调试 build 中,我们可以将这些零更改为有效的 ASCII 字符,以便在打印或解析时显示这些 bug。这些内容很容易被忽略。
始终将字符串移出代码行
C/C++ 绑定可以为字符串和 null 终止符分配空间,并复制和终止接收到的所有字符串。这会给 C 和低级 C++ 绑定带来不可接受的性能成本。
将字符串移出 ASan build 的行
地址清理器将检查代码是否不会超出堆分配。对于 ASan build,我们可以在堆上为字符串分配空间,将字节复制到该空间,然后将该指针返回给调用方。这很难集成到 C 绑定中,可能意味着会隐藏 ASan 和发布 build 之间行为的重大差异,并且无法帮助在 Fuchsia build 系统之外工作的开发者。
停止使用 C 和 C++
如果 Google 在 2019 年都无法编写安全的 C++,那么 C++ 就无法安全使用。遗憾的是,C 和 C++ 是许多设备驱动程序作者的首选语言,许多供应商可能会选择将其现有的 C/C++ 驱动程序移植到我们的平台。
不向 C 公开原始字符
我们可以向 C 公开一个不透明的结构,并要求 C 中的任何字符串访问都从解码后的消息缓冲区中复制字符串。
在先技术和参考资料
DBus、Capt'n Proto 和 CORBA1 会发送长度并以 null 结尾,而 protobuf 不会以 null 结尾。
-
CORBA 规范 3.3,第 2 部分,第 9.3.2.7 节。 ↩