FlatBuffers C++ 如何校验来自网络的不可信缓冲区?
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
在 C++ 中读取 FlatBuffer 时,生成代码里的访问器函数是通过偏移量(offset)直接定位字段的,这些偏移量在运行时并不做检查。这意味着一个格式损坏、长度截断,或者被攻击者篡改过的缓冲区,可能让程序访问到缓冲区之外的内存而崩溃。FlatBuffers 官方文档在 docs/source/languages/cpp.md 的 “Access of untrusted buffers” 一节明确区分了两种数据源:如果是你自己生成的、来源可信的磁盘数据,不校验可以接受;但如果数据来自网络且可能被篡改,就应当在访问数据之前使用缓冲校验器(buffer verifier)。
校验器会检查所有偏移量、所有字段的大小,以及字符串的空终止符,保证后续对缓冲区的一切读取都落在缓冲区内部。本文按官方文档给出的路径,说明如何在 C++ 读取网络数据的流程中接入这一校验。
前提:已有 schema 编译生成的头文件
文档假设你已经完成了 schema 编写与编译这一步(见 docs/source/flatc.md 与 docs/source/schema.md)。以仓库中的monster_test.fbs为例,用 Schema 编译器生成 C++ 头文件:
flatc -c monster_test.fbs生成的monster_test_generated.h依赖flatbuffers/flatbuffers.h,该头文件需要位于你的 include 路径中。关键的一点:每个根类型(root type)都会生成对应的校验函数。例如对于根类型Monster,生成代码里就有VerifyMonsterBuffer,可以在 tests/monster_test_generated.h 看到它的定义。如果你的根类型不是Monster,对应的函数名也是Verify<根类型名>Buffer。
校验的基本用法
在拿到网络收到的缓冲区之后、调用任何GetXxx()访问器之前,先构造校验器并执行校验。docs/source/languages/cpp.md 给出的示例是:
Verifier verifier(buf, len); bool ok = VerifyMonsterBuffer(verifier);其中buf是缓冲区起始指针,len是缓冲区长度,二者就是你从网络层(socket、RPC 框架等)拿到的数据。判断方法以文档原文为准:如果ok为 true,该缓冲区是安全的,可以读取;此时再按常规方式取根对象,例如auto monster = GetMonster(buf);并通过monster->hp()等访问器读取字段。ok为 false 时说明缓冲区畸形,应当丢弃该数据并走你自己的错误处理,而不是继续访问。
仓库的测试代码展示了同样的接入方式,tests/monster_test.cpp 中AccessFlatBufferTest在访问缓冲区的第一步就是校验:
void AccessFlatBufferTest(const uint8_t* flatbuf, size_t length, bool pooled) { // First, verify the buffers integrity (optional) flatbuffers::Verifier verifier(flatbuf, length); // ... TEST_EQ(VerifyMonsterBuffer(verifier), true); // 校验通过后才从根对象开始访问 auto monster = GetMonster(flatbuf);测试套件对合法缓冲区断言校验返回 true,这就是校验通过与否的直接判据。
限制嵌套深度与表格数量(防拒绝服务)
文档指出,在可能出现拒绝服务攻击的专用场景下,校验器构造函数提供两个额外参数,用于限制校验器在判定缓冲区畸形之前允许遇到的最大嵌套深度和最大表格(table)总数:
// 默认等价于: Verifier verifier(buf, len, 64 /* max depth */, 1000000 /* max tables */);文档说明这个默认值“对大多数用途是足够的”。实现细节见 include/flatbuffers/verifier.h:VerifierTemplate的Options结构体中max_depth默认 64、max_tables默认 1000000,另外还有check_alignment(为 true 时校验所有数据对齐)和check_nested_flatbuffers等选项;头文件同时保留了带max_depth、max_tables、check_alignment参数的那个构造函数。如果你判断网络数据天然嵌套很深或表格很多,可以显式调大这两个上限,而不是让校验把正常数据误判为畸形。
校验的开销与适用边界
文档对该功能的定位比较明确,写作时可以注意这几点:
- 校验不是免费的,但通常比一次完整遍历更快,因为标量数据实际不会被触碰;而且校验可能先把缓冲区读入缓存,后续真正读取时的开销可能比预期更低。
- 除了不可信数据,文档还建议在debug 模式下也调用一次校验,作为防止数据在传输/落盘途中被损坏的额外保险。
- 对来源可信的自有数据(例如你写在自己磁盘上的数据),文档认为跳过校验可以接受——这是取舍,不是强制要求。
仓库的 fuzz 测试 tests/fuzzer/flatbuffers_verifier_fuzzer.cc 展示了校验器的最简完整用法:对任意输入字节流直接构造校验器并执行VerifyMonsterBuffer,返回结果即校验结论。
结果判断与限制汇总
| 情况 | 依据 |
|---|---|
VerifyMonsterBuffer(verifier)返回 true | 缓冲区通过偏移量、字段大小与字符串空终止检查,可读 |
| 返回 false | 缓冲区畸形(偏移越界、大小非法或超出深度/表格数上限),不应访问 |
| 来源可信的自有数据 | 文档认为可接受不校验 |
| debug 构建 | 文档建议调用校验作为数据损坏的额外保险 |
需要注意的边界:校验器保证的是“后续读取都落在缓冲区内部”,它不校验业务语义(比如某个字符串内容是否合法),也不替你处理校验失败后的协议层行为;这些仍由你的应用代码负责。若要查看完整实现,可参考 include/flatbuffers/verifier.h 中的VerifierTemplate;若要在项目中构建并运行 C++ 测试来确认校验行为,参考 docs/source/building.md,然后在仓库根目录执行./flattests(C++ 文档中说明的测试运行方式)。
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考