/* * Entry point from instrumented code. * This is called once per basic-block/edge. */ void notrace __sanitizer_cov_trace_pc(void) { structtask_struct *t; unsignedlong *area; unsignedlong ip = canonicalize_ip(_RET_IP_); unsignedlong pos;
t = current; if (!check_kcov_mode(KCOV_MODE_TRACE_PC, t)) return;
area = t->kcov_area; /* The first 64-bit word is the number of subsequent PCs. */ pos = READ_ONCE(area[0]) + 1; if (likely(pos < t->kcov_size)) { area[pos] = ip; WRITE_ONCE(area[0], pos); } } EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
Syzkaller 与 AFL 一致,是使用边覆盖率来统计覆盖情况的,因此他会将实际记录得到的每个基本块的记录值 PC 和前一个基本块的 PC 值进行一个哈希之后的异或值来唯一标识一条边,将该值称为 signal ,这个过程中存在过滤函数和去重函数,会进行一定的筛选。然后 Syzkaller 通过维护一个字典,该字典的键是 signal 来判断每次执行是否有新覆盖产生。
voidwrite_coverage_signal(cover_t* cov, uint32* signal_count_pos, uint32* cover_count_pos, uint32* hit_num_pos) { // Write out feedback signals. // Currently it is code edges computed as xor of two subsequent basic block PCs. cover_data_t* cover_data = (cover_data_t*)(cov->kcov_area + cov->data_offset); if (flag_collect_signal) { uint32 nsig = 0; cover_data_t prev_pc = 0; bool prev_filter = true; for (uint32 i = 0; i < cov->size; i++) { cover_data_t pc = cover_data[i] + cov->pc_offset; uint32 sig = pc; if (use_cover_edges(pc)) sig ^= hash(prev_pc); bool filter = coverage_filter(pc); // Ignore the edge only if both current and previous PCs are filtered out // to capture all incoming and outcoming edges into the interesting code. bool ignore = !filter && !prev_filter; prev_pc = pc; prev_filter = filter; if (ignore || dedup(sig)) continue; write_output(sig); nsig++; } // Write out number of signals. *signal_count_pos = nsig; } //... }
switch (size) { case8: type |= KCOV_CMP_SIZE(0); break; case16: type |= KCOV_CMP_SIZE(1); break; case32: type |= KCOV_CMP_SIZE(2); break; case64: type |= KCOV_CMP_SIZE(3); break; default: return; } for (i = 0; i < count; i++) write_comp_data(type, cases[i + 2], val, _RET_IP_); } EXPORT_SYMBOL(__sanitizer_cov_trace_switch); #endif/* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
这是 LLVM 中的代码,情况和描述也一致
1 2 3 4 5 6 7 8 9
// Called before a switch statement. // Val is the switch operand. // Cases[0] is the number of case constants. // Cases[1] is the size of Val in bits. // Cases[2:] are the case constants. void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases);
// 然后根据 case 的值的位,直接调用相应的写函数(所以不会被__sanitizer_cov_trace_const_cmp*捕获到) // 枚举所有 case,每个单独写一次,就写:比较位数,case值,当前switch值,返回地址.
// Write order: type arg1 arg2 pc. write_output((uint32)type);
// KCOV converts all arguments of size x first to uintx_t and then to // uint64. We want to properly extend signed values, e.g we want // int8 c = 0xfe to be represented as 0xfffffffffffffffe. // Note that uint8 c = 0xfe will be represented the same way. // This is ok because during hints processing we will anyways try // the value 0x00000000000000fe. switch (type & KCOV_CMP_SIZE_MASK) { case KCOV_CMP_SIZE1: arg1 = (uint64)(longlong)(signedchar)arg1; arg2 = (uint64)(longlong)(signedchar)arg2; break; case KCOV_CMP_SIZE2: arg1 = (uint64)(longlong)(short)arg1; arg2 = (uint64)(longlong)(short)arg2; break; case KCOV_CMP_SIZE4: arg1 = (uint64)(longlong)(int)arg1; arg2 = (uint64)(longlong)(int)arg2; break; } bool is_size_8 = (type & KCOV_CMP_SIZE_MASK) == KCOV_CMP_SIZE8; if (!is_size_8) { write_output((uint32)arg1); write_output((uint32)arg2); } else { write_output_64(arg1); write_output_64(arg2); } }
0x04 逆向分析测试
有了上述源代码分析过程,还可以将编译完成的 vmlinux 放入 IDA 中对其进行查看,可以看到确实只有必要的基本块被插桩了,其余的基本块可以通过这些插桩的基本块来计算出是否经过了。
而当我们在编译内核时,打开了 CONFIG_KCOV_ENABLE_COMPARISONS 时,再进行编译得到 vmlinux 后,通过 IDA 反汇编也能看到对比较指令的插桩情况。
0x05 动态分析
内核编译好之后,通过其调试信息和记录的返回地址可以很好将执行情况与源码进行对应。 Syzkaller也是基于这一技术在 web 端进行实时代码行数覆盖监控的。
$ addr2line -afip -e vmlinux 0xffffffff81730ff1: __x64_sys_read at ??:? 0xffffffff81730deb: ksys_read at ??:? 0xffffffff817a9840: __fdget_pos at ??:? 0xffffffff817a5f62: __fget_light at file.c:? 0xffffffff817a5fe2: __fget_light at file.c:? 0xffffffff817a60f3: __fget_light at file.c:? 0xffffffff817a98b0: __fdget_pos at ??:? 0xffffffff81730f1e: ksys_read at ??:? 0xffffffff810a43c6: fpregs_assert_state_consistent at ??:? 0xffffffff810a4414: fpregs_assert_state_consistent at ??:? 0xffffffff810a4444: fpregs_assert_state_consistent at ??:? 0xffffffff81313f76: exit_to_user_mode_prepare at common.c:?