Page 22 - 《软件学报》2024年第6期
P. 22
2598 软件学报 2024 年第 35 卷第 6 期
在利用 Go 的 ABI-Internal 获得 Go 的运行时栈信息时, 我们发现旧版本的 Go (Go1.16.15 及以下) 的 ABI 与
较新版本 Go (Go1.16.15 以上) 现行的 ABI-Internal 不同. 旧版本 Go 中, g 对象的地址不在寄存器 R14 中, 且旧版
本的 Go 并没有相应的 ABI 文档. 为了了解如何获得旧版本 Go 中的运行时信息, 我们对旧版本的 Go 的编译运行
时系统进行了人工分析. 最终发现在旧版本 Go 中, g 对象的地址存放在 TLS (thread local storage) 中的固定位置,
作为线程本地存储的一部分. 为了区分新版本和老版本的 Go, DBI-Go 在加载二进制时, 会首先获得系统 Go 的版
本, 根据 Go 的版本采取不同的策略. 针对 Go1.16.15 以上的 Go, 会使用 Go 现行的 ABI-Internal 从寄存器 R14 中
获得 g 对象的地址. 在 Go1.16.15 及以下的 Go 中, 则会从 TLS 中的固定位置获得 g 对象的地址, 并随后获得运行
时栈信息.
获得相应的栈信息后, 即可检测某地址是否在当前 Goroutine 栈中, 随后可结合规则 1 和规则 2 判断该 store
是否违反 Go 的逃逸不变式. 若该 store 违反了逃逸不变式, 就会结合该指令的地址获得其所在的函数, 并向 log 文
件中输出相应的出错信息, 包括该指令的地址、所在的函数、违反不变式的原因以及当前的运行时栈信息. 这些
信息可以在之后帮助开发者更快地找到问题.
3.4 采用多种措施减少误报
为了减少误报, DBI-Go 主要采取了以下措施.
(1) 措施 1: 过滤掉非 Go 函数. Go 的运行时最终以静态链接库的形式和用户代码链接成可执行文件, 其中除
了 Go 函数外还包括许多汇编和 C 函数. 汇编和 C 函数不遵守 Go 的 ABI 约定, 对这些函数进行分析会得到错误
的结果. 同时, 这些非 Go 函数也不遵守 Go 的逃逸不变式, 因此也无需对其进行分析. Go 的代码以包 (package) 的
形式进行管理, 每个函数都有其所在的包. 基于此观察, DBI-Go 采用基于模式匹配的方法, 通过函数名判断每个函
数是否在某个包内, 并据此过滤掉所有非 Go 函数.
(2) 措施 2: 过滤掉 Go 运行时函数. Go 除了会在用户代码中生成若干与运行时管理相关的代码外, 还会使用
runtime 包中的运行时函数来进行运行时管理. 这些运行时函数会产生若干违反 Go 逃逸不变式的 store 操作, 但这
些操作由 Go 的运行时保证了其安全性. 因此在 DBI-Go 的实现中会过滤掉 runtime 包中的函数以避免误报.
(3) 措施 3: 过滤掉非指针 store. 利用第 3.2.3 节中的方法, DBI-Go 可以恢复 Go 二进制中 store 指针的语义, 过
滤掉不包含指针的对象的 store. 使用该措施可以大大降低将非指针类型诸如 int、uintptr 等当作指针从而带来的
误报, 提升 DBI-Go 对已知漏洞的覆盖情况如何, 能否发现新的漏洞?
的分析精度.
以上措施不仅可以降低误报, 还降低了 DBI-Go 的整体开销. 通过上述措施, 我们提升了 DBI-Go 的精度和分
析效率 (详见第 4.3 节).
4 实验评估
对 DBI-Go 的实验评估在 x86-64 的机器上进行. 实验环境如下所示.
• 操作系统: Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-48-generic x86_64).
• CPU: 2 × AMD EPYC 7 763 64-Core Processor.
• 内存: 1.0 TiB.
• 涉及的 Go 版本: Go1.11 至 Go1.20.5.
实验试图回答以下研究问题.
(1) DBI-Go
(2) DBI-Go 插桩的回调函数带来的额外开销有多少, 是否满足了轻量快速的目标?
(3) DBI-Go 的适用性如何, 能否在不同的 Go 版本上正常使用?
4.1 有效性测试
为了对 DBI-Go 的漏洞覆盖率进行测试, DBI-Go 测试了图 1 中目前所有已知的社区例子. 针对图 1 中目前可
以复现且有着复现例子的 issue, 我们使用对应的 Go 版本将这些例子编译, 并之后使用 DBI-Go 插桩. 图 1 中提供