GraphFuzz源码解析

GraphFuzz源码解析

  • 核心代码位于core目录下
    • graph.hpp为图结构体头文件,主要包含一个结构体NodeLink和一个类TGraph
    • schema.hpp声明了一个类Schema,同时包含ScopeDefSignaturehashScopeTreeTypeTree这五个结构体
    • harness.cpp为生成的harness文件,该文件调用libfuzzer的接口来实现相关功能

harness.cpp

  • LLVMFuzzerInitialize()

    该函数为libfuzzer提供的接口,用于进行模糊测试前的初始化操作:

    • for循环体内对命令行参数进行解析:
    1
    2
    3
    for (int i = 0; i < *argc; ++i) {
    // do something to parse cmd line
    }

    共有14个命令行选项:

    支持的命令行选项 描述
    –graphfuzz_debug 是否启用debug
    –graphfuzz_skip_validation 是否跳过有效性验证
    –graphfuzz_prune_cache 是否精简缓存
    –graphfuzz_enable_soft_execution 是否启用软执行(有点类似于forkserver)
    –graphfuzz_ignore_invalid 是否忽略无效的模式(默认false)
    –graphfuzz_trace_mutations 是否跟踪变异,如果启用,则会保存相关日志
    –graphfuzz_catch= 捕获那些异常信号,值为若干个信号编号,用逗号隔开
    –graphfuzz_scope_max_depth= 设置最大scope_max_depth,默认值为10
    –graphfuzz_context_mutation_prob= 对上下文变量进行变异的概率,默认为0.95
    –graphfuzz_max_nodes= 每个图最大节点数,默认为200
    –graphfuzz_schema= 模式文件重命名,默认为”schema.json”
    –graphfuzz_mutate_one –graphfuzz_mutate_one <seed> <input> <output>
    在初始化阶段对种子进行一次变异
    –graphfuzz_init_corpus –graphfuzz_init_corpus <corpus>
    初始化种子
    • Schema::FromFile()schema.json中读取模式文件并保存到Schema *global_schema变量中

    • 调用Schema::Validate()函数来验证schema是否有效,主要检查每个类是否包含构造或析构函数

    • register_signals():注册信号,主要为 graphfuzz_catch 变量指定的需要捕获的信号添加句柄函数sig_handler,该函数使用siglongjmp()函数让程序跳转到先前配置的跳转点

    • 对种子/语料库进行变异或初始化操作(二选一)

    • global_init(orig_argc, orig_argv)执行fuzz_exec.cpp定义的相关方法,用于对参数进行初始化操作,默认情况下为空


  • MutateOne()
    • 调用LLVMFuzzerCustomMutator()对种子进行变异,然后将变异后的信息保存到--graphfuzz_mutate_one指定的output中

  • LLVMFuzzerCustomMutator()

    该函数为libfuzzer提供的接口,用于进行自定义的变异:

    • 首先根据模式构建TGraph变量g,然后g读入种子数据并验证其有效性

      • 如果读入失败或无法通过有效性验证,则根据种子重新创建一个新的图
      • 否则,则调用TGraph::Mutate方法进行图变异(十选一)
    • 将生成的图序列化为字符串,并将变异后的数据更新回缓冲区


  • LLVMFuzzerCustomCrossOver()

    该函数为libfuzzer提供的接口,用于两个种子之间自定义交叉变异


  • LLVMFuzzerTestOneInput()

    该函数为libfuzzer提供的接口,为libfuzzer主模糊测试模块,用于将生成的数据投喂给目标

    • sigsetjmp()函数在LLVMFuzzerTestOneInput()起始处设置跳转点

    • 调用shim_init()函数,确保已经获得覆盖信息

    • 构建一个TGraph(模板图)的变量g,然后读入生成的数据(是否成功读入),并检验其合法性(是否有构造和析构)

    • 调用TGraph类的GetOrderedNodes()方法得到layer递增的节点序列,然后遍历每一个节点:

      1
      void *ref[nodes.size()][MAX_CONN]; // 生成一个二维指针数组
      • 首先载入输入,具体来说:
      1
      2
      3
      void *in_ref[n.in_ref_size()];
      // ...
      in_ref[i] = ref[n.index()][i]; // 将指针值赋值给in_ref[i],显然默认为0
      • 然后调用函数端点,
      1
      2
      void (*func)(void **, void **, const char *) = FUZZER_SHIMS[n.type()];
      func(in_ref, out_ref, context); // 将in_ref, out_ref和context投喂给端点
      • 最后,将输出拷贝到结果中(注:结果在函数端点调用时已经写到out_ref中),
      1
      2
      3
      4
      5
      6
      7
      for (int i = 0; i < n.out_ref_size(); ++i) {
      NodeRef r = n.out_ref(i);
      ref[r.node_idx()][r.conn_idx()] = out_ref[i]; // 如果当前输出作为下一个连接节点的输入,那么更新ref数组
      if (graphfuzz_debug) {
      cout << "Got output: " << i << " :: " << out_ref[i] << endl;
      }
      }

    • 调用shim_finalize()


  • graphfuzz_try()
    • 用于实现软执行,在主模糊测试流程中未使用

graph.hpp

  • 该头文件定义了一些图相关的结构体和方法,其中最重要的是Mutate()方法

  • Mutate()

    调用链🔗harness.cppMutateOne() $\rightarrow$ LLVMFuzzerCustomMutator() $\rightarrow$ TGraph::Mutate()

    分为两类变异:

    • 上下文变异MutateContext()【概率:默认为0.95】
    • 基于图变异(九选一)【具体详见论文】:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    switch (mut_choice) {
    case 0: MutateCrosslink(); break;
    case 1: MutateTruncateDestructor(); break;
    case 2: MutateTruncateConstructor(); break;
    case 3: MutateLayerIndex(); break;
    case 4: MutateSwapEquivalent(); break;
    case 5: MutateExtendDestructor(); break;
    case 6: MutateExtendConstructor(); break;
    case 7: MutateSpliceIn(); break;
    case 8: MutateSpliceOut(); break;
    }

    🤔 存在问题:变异方法调度仅使用概率,可能存在某种harness序列上下文过度变异【同时可能会导致图变异饿死】或者上下文变异不足【导致bug遗漏,可能会在后面图变异生成相同的变异模式(或类似的变异模式?)】


GraphFuzz源码解析
http://bladchan.github.io/2022/07/25/GraphFuzz源码解析/
作者
bladchan
发布于
2022年7月25日
许可协议