[protocolbuffers/protobuf]protoc 允许源代码和字符串类型的选项值中存在无效的 UTF-8

2024-05-11 511 views
9

您使用什么版本的 protobuf 以及什么语言? 最新的:

> protoc --version
libprotoc 3.19.1

什么操作系统(Linux、Windows...)和版本?

操作系统 X 11.6.1

您使用什么运行时/编译器(例如,python 版本或 gcc 版本)

不适用

你做了什么?

我编译了一个包含无效二进制数据的文件。虽然规范没有规定源代码应该采用 UTF-8 编码,但 GitHub 中的这个问题表明这是正确的:#1418。 (注意:为了清楚起见,确实应该更新文档以明确说明这一点。)

为了使源成为有效的可编译源,我将其放入类型为 的选项的字符串文字中string。我实际上创建了两个在语义上等效的文件:

// tmp.proto
syntax = "proto3";

package foo;

option java_outer_classname= "my?value";

上面的问号实际上是二进制值0xbc。要使用此示例文件的二进制数据,您可以使用xxd -r以下命令:

00000000: 7379 6e74 6178 203d 2022 7072 6f74 6f33  syntax = "proto3
00000010: 223b 0a0a 7061 636b 6167 6520 666f 6f3b  ";..package foo;
00000020: 0a0a 6f70 7469 6f6e 206a 6176 615f 6f75  ..option java_ou
00000030: 7465 725f 636c 6173 736e 616d 653d 2022  ter_classname= "
00000040: 6d79 bc76 616c 7565 223b 0a              my.value";.

第二个文件使用转义序列:

// tmp2.proto
syntax = "proto3";

package foo;

option java_outer_classname= "my\xbcvalue";

因此tmp.proto在源中包含无效的 UTF8 输入。并tmp2.proto在源中包含有效的 UTF8,但它定义了一个具有无效 UTF8 数据的字符串常量。

我只是将文件编译为描述符集:

protoc -o tmp.protoset tmp.proto
protoc -o tmp2.protoset tmp2.proto

你期望看到什么

我期望protoc

  1. 使用UTF 替换字符替换无效数据 � (U+FFFD)
  2. 或者抱怨输入的 UTF-8 格式不正确。

对于第一个文件,它应该抱怨源程序本身的输入。

对于第二个文件,它应该抱怨字符串选项的值不是有效输入(因为根据文档 字符串预计为 UTF-8 或 7 位 ASCII )。

你看到了什么?

这两个文件都编译成功。除了文件名之外,它们还生成相同的描述符:

> protoc --decode google.protobuf.FileDescriptorSet google/protobuf/descriptor.proto < tmp.protoset
file {
  name: "tmp.proto"
  package: "foo"
  options {
    java_outer_classname: "my\274value"
  }
  syntax: "proto3"
}

\274转义字节,而不是 unicode point 0xbc。这不是有效的 UTF8,但该java_outer_classname选项定义为 type string

回答

8

我怀疑由于 #7364,可能允许字符串选项的无效 UTF-8 值。但也可能是由于选项解释的奇怪方式(它们首先被直接解释为“未知字节”,然后尝试将它们解组到选项消息中)。

0

@googleberg,嗨!我看到这是大约一个月前分配的。您希望这个问题能够得到解决吗?如果我调查它,你会审查并可能接受它的拉取请求吗?

2

我可以代表他回答:是的!如果您愿意尝试一下,我们很乐意进行审核。

1

对于延迟,我深表歉意。绝对地!

6

@perezd,@googleberg,酷,我现在正在看。

这个问题实际上有两个部分:

  1. 文件输入是有效的 UTF8
  2. 字符串常量包含有效的 UTF8

对于第一部分,我将向标记器添加一个标志以启用 UTF8 验证。目前,只有解析器会设置它。我担心否则“爆炸半径”可能会太大。 (同一个类还用于解析文本格式——可能也需要 UTF8 输入,但这在向后兼容性方面似乎也存在更大的风险。)有一些微妙之处,例如多字节代码点跨越了分词器缓冲区的边界,实现起来并不像在测试中验证那么棘手。但我认为第一部分是更容易的部分。

第二部分看起来更复杂。检查本身很简单(google/protobuf/stubs文件夹中已经有一个函数可以执行此操作),并且我认为只有两个地方可以调用它 - 当处理/类型检查字段的默认值和解释所有其他选项的代码时。然而,代码库中已经有很多关于是否检查字符串是否为 UTF8 的内容。该代码似乎引用了实际不存在的文件和字段选项descriptor.proto(可能是 Google 内部迁移的一部分)?它将 UTF8 检查推迟到各个运行时——因此 C++ 运行时可以像 Java 运行时一样实现自己的检查。因此,解决第二部分似乎不太清楚如何进行(也许风险更高?)。我不确定是否有一种简单的方法可以启用签入,protoc但仍然可以轻松地禁用它,例如通过环境变量或预处理器定义在编译时将其关闭。

你有什么建议吗?

2

@jhump 我相信你的评估是正确的。弄清楚如何仅将 UTF-8 强制应用于那些可以保证安全的字符串类型选项字段,这似乎是很模糊的。看来强制执行需要在descriptor.cc中进行:bool DescriptorBuilder::OptionInterpreter::SetOptionValue

我想知道我们是否可以在这些字段上使用显式字段选项“enforce_utf8”。如果是这样,我认为我们可以简单地修改descriptor.proto,以便像java_outer_classname这样的字符串类型选项声明如下:可选字符串java_outer_classname = 8 [enforce_utf8 = true];

force_utf8 被记录为已弃用,但那是在 proto3 的上下文中。对于proto2来说,似乎还是有用的。让我检查一下。

2

force_utf8 被记录为已弃用

@googleberg,就像我提到的,虽然代码引用了这样的选项,但它实际上并不存在于开源版本中。看一下: https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto#L539

我认为您可能正在查看 的内部版本descriptor.proto。许多与强制执行 UTF8 相关的评论表明这是为了内部迁移(也许迁移到实际强制执行 UTF8?)。

文档(对于proto2proto3)指出字符串必须始终是有效的 UTF8。

1

@jhump确实,它只是内部的,但它可能值得外部化,除了它只适用于关闭 proto3 中的 UTF8 验证。它从来不允许在 proto2 中打开 UTF8 验证。

进行全面强制执行的问题在于,无效的 UTF8 几乎肯定已被放入 Google 内的字符串类型自定义选项中(尽管有文档)。

我们目前正在考虑一些选项,这些选项可能会允许逐步引入描述符扩展的强制执行。我将研究路线图,看看我们是否可以利用这些。