使用多级中间表示(MLIR)和VAST在C代码中查找漏洞

本文介绍了Trail of Bits团队开发的VAST工具,它通过构建多级中间表示(MLIR)的"抽象塔",帮助分析人员在不同抽象层级上查找C/C++代码中的漏洞,并以Sequoia缓冲区溢出漏洞为例演示了如何编写基于VAST的漏洞检测器。

使用多级中间表示和VAST在C代码中查找漏洞

中间表示(IR)是逆向工程师和漏洞研究人员用来"见树又见林"的工具。IR让我们能在不同抽象层级上观察程序,从而既能分析底层代码异常,也能发现高层逻辑错误。但现有工具的局限在于它们通常被限制在特定IR层级,而漏洞可能存在于任何抽象层级。

我们开发的VAST工具通过提供"IR抽象塔"解决了这个问题,允许分析从最适合的IR层级开始,然后根据需要上下切换。例如分析栈缓冲区溢出时:(1)在LLVM IR层级最容易识别;(2)在接近机器代码层级才能判断是否构成安全威胁;(3)在最高级的AST层级才能理解根本原因。

VAST的中间表示抽象塔

VAST的核心是基于MLIR(多级中间表示)项目构建的。MLIR是LLVM相关的基础设施项目,它通过"方言"(dialects)机制支持领域特定语言和IR的开发。VAST使用MLIR为C/C++编译过程的每个抽象层级构建对应的方言,形成从AST到LLVM IR的完整IR塔。

编写基于VAST的漏洞检测器

我们以Linux内核中的Sequoia漏洞为例,演示如何编写检测器。该漏洞的变体出现在seq_buf_path函数中,它将无符号的size_t值传递给d_path函数的有符号int参数,可能导致整数溢出。

  1. 首先设置基本的MLIR分析环境,注册必要的方言和检测Pass:
1
2
3
4
5
6
7
auto main(int argc, char** argv) -> int {
  mlir::DialectRegistry registry;
  vast::registerAllDialects(registry);
  register_sequoia_checker_pass();
  return mlir::failed(
      mlir::MlirOptMain(argc, argv, "VAST Sequoia Bug Checker\n", registry));
}
  1. 实现检测Pass的核心逻辑,主要检查:
  • 是否存在无符号到有符号的类型转换
  • 转换后的参数是否用于指针运算
1
2
3
4
5
6
7
8
9
auto is_unsigned_to_signed_cast(mlir::Operation* opr) -> bool {
  // 检查是否为整型转换且从无符号转为有符号
  ...
}

static auto has_ptr_arith_use(mlir::Operation* opr) -> bool {
  // 递归检查是否用于指针运算
  ...
}
  1. 在函数操作中遍历所有调用点应用检测逻辑:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void runOnOperation() override {
  auto check_for_sequoia = [&](CallOp call) {
    for (const auto& arg : llvm::enumerate(call.getArgOperands())) {
      if (is_unsigned_to_signed_cast(arg.value().getDefiningOp())) {
        if (llvm::any_of(param.getUsers(), has_ptr_arith_use)) {
          // 报告漏洞
        }
      }
    }
  };
  getOperation().walk(check_for_sequoia);
}

运行检测器成功发现了示例代码中的漏洞:

1
Call to `d_path` in `seq_buf_path` passes an unsigned value to a signed argument...

在各级抽象中寻找漏洞

不同漏洞在不同抽象层级更容易被发现。VAST让工具开发者可以选择最适合的IR层级进行定制化分析。我们展示了如何利用VAST的高层(hl)方言检测Sequoia漏洞,这种方法也可应用于其他逆向工程和漏洞研究项目。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计