GraphFuzz源码解析
GraphFuzz源码解析
- 核心代码位于
core
目录下graph.hpp
为图结构体头文件,主要包含一个结构体NodeLink
和一个类TGraph
schema.hpp
声明了一个类Schema
,同时包含ScopeDef
、Signature
,hash
,ScopeTree
和TypeTree
这五个结构体harness.cpp
为生成的harness
文件,该文件调用libfuzzer
的接口来实现相关功能
harness.cpp
LLVMFuzzerInitialize()
:该函数为
libfuzzer
提供的接口,用于进行模糊测试前的初始化操作:- for循环体内对命令行参数进行解析:
1
2
3for (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
3void *in_ref[n.in_ref_size()];
// ...
in_ref[i] = ref[n.index()][i]; // 将指针值赋值给in_ref[i],显然默认为0- 然后调用函数端点,
1
2void (*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
7for (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.cpp:
MutateOne()
$\rightarrow$LLVMFuzzerCustomMutator()
$\rightarrow$TGraph::Mutate()
分为两类变异:
- 上下文变异
MutateContext()
【概率:默认为0.95】 - 基于图变异(九选一)【具体详见论文】:
1
2
3
4
5
6
7
8
9
10
11switch (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遗漏,可能会在后面图变异生成相同的变异模式(或类似的变异模式?)】
- 上下文变异