学习笔记想法简友广场

DataFlowSanitizer

2021-09-09  本文已影响0人  佩玖吟

介绍

DataFlowSanitizer 是动态数据流分析工具,和其他 Sanitizer 工具不同,该工具本身并非用于检测一些指定类型的bug。而是提供一个通用的动态数据流分析框架,用户可以使用它来检测代码中的应用相关的问题。

如何使用 DFSan build libc++

DFSan 要求你的所有代码进行插桩,或者在 ABI 列表中列出来的函数可以不进行插桩。

如果你想要得到插桩过的 libc++ 函数,你需要使用 DFSan 插桩,从源码来 build。下面展示了如何使用 DFS 插桩来 build libc++ 和 libc++ ABI。

cd libcxx-build

# An example using ninja
cmake -G Ninja path/to/llvm-project/llvm \
  -DCMAKE_C_COMPILER=clang \
  -DCMAKE_CXX_COMPILER=clang++ \
  -DLLVM_USE_SANITIZER="DataFlow" \
  -DLLVM_ENABLE_LIBCXX=ON \
  -DLLVM_ENABLE_PROJECTS="libcxx;libcxxabi"

ninja cxx cxxabi

注意:确保使用最新版本的Clang来进行 build

使用

在没有程序更改的情况下,对程序应用 DFSan 不会改变程序的行为。为了使用 DFSan,程序使用 API 函数对数据添加标记以便对其进行跟踪,并且对特定的数据进行标签检查。DFSan 根据数据流向,来管理这些标签在程序中的传播。

API 定义在 sanitizer/dfsan_interface.h 中,要想获取每个函数的更进一步信息,请参考该头文件。

ABI 列表

DFSan 使用一个函数列表(即ABI列表),来决定一个指定函数的调用,应该使用操作系统的原生 ABI,还是应该使用通过函数参数和返回值来传播标记的 ABI 变体。在前一种情况下,ABI 列表文件也会控制标签的传播方式。

DFSan 有一个默认的 ABI 列表,意在最终覆盖 Linux 上的 glibc 库,但同时用户也可能在一些场景下对其ABI进行扩展:一个特定的库或函数无法使用编译器插桩(如使用汇编代码或者另外一种语言实现的,而DataFlowSanitizer并不支持该语言),或者从一个库中调用的函数无法使用编译器插桩。

DFSan 的 ABI 列表文件是 Sanitizer 的特例列表,pass 会将 ABI 列表文件中的未插桩类别的每个函数,当做是遵从原生ABI的。除非 ABI 列表包含有针对这些函数的额外类别,否则对这些函数的每一次调用都会生成一个警告信息,因为对函数的打标签操作是未知的。其他支持的类别是 discardfunctionalcustom

void f(int x);
void __dfsw_f(int x, dfsan_label x_label);

void *memcpy(void *dest, const void *src, size_t n);
void *__dfsw_memcpy(void *dest, const void *src, size_t n,
                    dfsan_label dest_label, dfsan_label src_label,
                    dfsan_label n_label, dfsan_label *ret_label);

如果一个定义在正在编译的转换单元中的函数属于未插桩的类别,那该函数必须编译以符合原生的ABI。它的参数会被认为是未标记的,但在影子内存中它会认为是有标记的。

# main is called by the C runtime using the native ABI.
fun:main=uninstrumented
fun:main=discard

# malloc only writes to its internal data structures, not user-accessible memory.
fun:malloc=uninstrumented
fun:malloc=discard

# tolower is a pure function.
fun:tolower=uninstrumented
fun:tolower=functional

# memcpy needs to copy the shadow from the source to the destination region.
# This is done in a custom function.
fun:memcpy=uninstrumented
fun:memcpy=custom

例子

以下程序通过检查是否传播了正确的标签来演示标签传播。

#include <sanitizer/dfsan_interface.h>
#include <assert.h>

int main(void) {
  int i = 100;
  int j = 200;
  int k = 300;
  dfsan_label i_label = 1;
  dfsan_label j_label = 2;
  dfsan_label k_label = 4;
  dfsan_set_label(i_label, &i, sizeof(i));
  dfsan_set_label(j_label, &j, sizeof(j));
  dfsan_set_label(k_label, &k, sizeof(k));

  dfsan_label ij_label = dfsan_get_label(i + j);

  assert(ij_label & i_label);  // ij_label has i_label
  assert(ij_label & j_label);  // ij_label has j_label
  assert(!(ij_label & k_label));  // ij_label doesn't have k_label
  assert(ij_label == 3);  // Verifies all of the above

  // Or, equivalently:
  assert(dfsan_has_label(ij_label, i_label));
  assert(dfsan_has_label(ij_label, j_label));
  assert(!dfsan_has_label(ij_label, k_label));

  dfsan_label ijk_label = dfsan_get_label(i + j + k);

  assert(ijk_label & i_label);  // ijk_label has i_label
  assert(ijk_label & j_label);  // ijk_label has j_label
  assert(ijk_label & k_label);  // ijk_label has k_label
  assert(ijk_label == 7);  // Verifies all of the above

  // Or, equivalently:
  assert(dfsan_has_label(ijk_label, i_label));
  assert(dfsan_has_label(ijk_label, j_label));
  assert(dfsan_has_label(ijk_label, k_label));

  return 0;
}

dfsan_label ij_label = dfsan_get_label(i + j); 可简单理解为,ij_label会同时追踪i和j两个int值对应的标签,所以在后边的assert中有对应的检查逻辑。

上一篇 下一篇

猜你喜欢

热点阅读