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  中提供
   17   18   19   20   21   22   23   24   25   26   27