RFC-0034:Null 终止字符串 | |
---|---|
状态 | 已拒绝 |
领域 |
|
说明 | 我们提议 FIDL 中的字符串以 null 结尾(一如既往,保留字符串的大小)。此外,将 C 绑定更改为针对字符串数据使用 uint8_t* |
作者 | |
提交日期(年-月-日) | 2019-02-08 |
审核日期(年-月-日) | 2019-02-21 |
遭拒原因
摘要
我们建议:
FIDL 中的字符串将以 null 终止(始终保持 字符串);
更改 C 绑定以将
uint8_t*
用于字符串数据。
设计初衷
当前的 FIDL 字符串编码很容易导致意外写入不安全的内容 代码。C 和低级别 C++ 绑定用于一些具有最高特权的 代码,因此应额外重视暴露风险 这些层。
我们不小心遇到了这样的情况 错误,但存在 对 Fuchsia 树中的代码进行系统性审核非常困难,不会阻止它们 出现在我们的代码树或第三方代码中。
设计
这提议更改 FIDL 字符串的编码,以包含单个 终止符字节,且值为零。这不会使 FIDL 字符串与 C 字符串,但 FIDL 字符串用作 C 字符串,系统会将其解释为 比预期短要比预期长,这更安全。
电汇格式
新的 的定义 线路编码的字符串如下(突出显示的更改):
- 表示文本的可变长度的 UTF-8 编码字符序列。
- 可为 null;null 字符串和空字符串不同。
- 可以指定大小上限,例如
string:40
,最多 40 个字节的字符串 (不包括 null 终止符)。 - 字符串内容含有 null 终止符。
- 编码器和解码器必须验证最后一个 字符串的字节,如长度指示。
- 存储为一条 16 字节的记录,其中包括:
<ph type="x-smartling-placeholder">
- </ph>
size
:64 位无符号代码单元(字节),不包括 null 终止符data
:64 位存在状态指示或指向外行字符串数据的指针
- 编码以进行传输时,
data
表示内容存在: <ph type="x-smartling-placeholder">- </ph>
0
:字符串为 nullUINTPTR_MAX
:字符串为非 null,数据是下一个外行对象
- 在解码以供使用时,
data
是一个指向内容的指针: <ph type="x-smartling-placeholder">- </ph>
0
:字符串为 null<valid pointer>
:字符串为非 null,data
在指示的内存中 地址
字符串的表示方式如下:
string
:不可为 null 的字符串(如果 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 用途。
在某些构建时标志后面,需要更新以下代码:
//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
。这会
有助于揭示这类错误,但不能阻止它们
在调试 build 中使用有效的 ASCII 填充内边距
8 个字符串中有 7 个后跟零填充字节。调试中 我们可以将这些零更改为有效的 ASCII 字符, 输出或解析后的错误这些东西很容易掉下来 穿越裂缝
始终将字符串移出一行
C/C++ 绑定可以为字符串和 null 终止符分配空间, 复制和终止收到的所有字符串。这会对 C 和低级别 C++ 绑定的性能损失是不可接受的。
将字符串移出 ASan build
地址排错程序将检查代码是否不会溢出堆分配。对于 ASan 构建,我们可以为堆上的字符串分配空间,复制字节 并将该指针返回给调用方。集成起来并非易事 绑定至 C 绑定,可能意味着在行为方面存在隐藏 bug 的显著差异 发布 build,并且不会帮助开发者 Fuchsia 构建系统。
停止使用 C 和 C++
如果 Google 在 2019 年无法编写安全的 C++ 代码,那么 C++ 就不安全了。很遗憾 C 和 C++ 是许多设备驱动程序作者的首选语言, 供应商可以选择将现有 C/C++ 驱动程序移植到我们的平台。
不要向 C 公开原始字符
我们可以向 C 公开一个不透明结构,并需要访问 C 中的任何字符串 从已解码的消息缓冲区中复制字符串。
早期艺术作品和参考资料
DBus、Capt'n Proto 和 CORBA1 发送长度和 null 终止,protobuf 终止。
-
CORBA 规范 3.3、 第 2 部分, 第 9.3.2.7 节。↩