afl++ parallel分析

afl++源码为例。分析slave在同步master的queue case的逻辑过程

parallel_fuzzing.md文档中提到,只有main节点才会和所有其他节点同步,secondary节点只会和main节点同步。

For performance reasons only -M main node syncs the queue with everyone, the -S secondary nodes will only sync from the main node.

如果是slave或者afl_import_first定义为1,或者queue_cycle为1,刚开始会进行一次同步。slave不会在每次fuzz_one结束后都进行和master的同步,而是每5次(由SYNC_INTERVAL宏定义控制)的fuzz_one后进行一次同步。

// afl-fuzz.c
skipped_fuzz = fuzz_one(afl);

if (!skipped_fuzz && !afl->stop_soon && afl->sync_id) {

    if (!(sync_interval_cnt++ % SYNC_INTERVAL)) { sync_fuzzers(afl); }

}

然后进入位于afl-fuzz-run.csync_fuzzers函数中。
首先扫描sync下的所有除了自身的fuzz实例文件夹。如果扫描者是slave的话,目标扫描的就是master fuzz。而是否是master fuzz是由在对应fuzz文件夹中是否有is_main_node文件来判断的

// afl-fuzz-run.c
sd = opendir(afl->sync_dir);
if (!sd) { PFATAL("Unable to open '%s'", afl->sync_dir); }

afl->stage_max = afl->stage_cur = 0;
afl->cur_depth = 0;

/* Look at the entries created for every other fuzzer in the sync directory.
*/

while ((sd_ent = readdir(sd))) {

    u8  qd_synced_path[PATH_MAX], qd_path[PATH_MAX];
    u32 min_accept = 0, next_min_accept;

    s32 id_fd;

    /* Skip dot files and our own output directory. */

    if (sd_ent->d_name[0] == '.' || !strcmp(afl->sync_id, sd_ent->d_name)) {

        continue;

    }

    entries++;
...

然后在确保目标fuzz目录下有queue文件夹后,扫描该queue文件夹下的所有用例,获取queue中的test case数量


    /* Skip anything that doesn't have a queue/ subdirectory. */

    sprintf(qd_path, "%s/%s/queue", afl->sync_dir, sd_ent->d_name);

    struct dirent **namelist = NULL;
    int             m = 0, n, o;

    n = scandir(qd_path, &namelist, NULL, alphasort);

    if (n < 1) {

      if (namelist) free(namelist);
      continue;

    }

然后在slave的.synced文件夹下面创建一个名为当前被扫描的fuzz示例名字的文件(文件内容为index。用于标注当前已经扫描存储进来的test case的index,即该index从0开始,逐一获取main queue里的id:index测试用例,并递增更新对应的index),

源码中n变量表示queue中的test case总数,m变量表示queue中未获取过的test case的最小index。此处mn分别表述了我们要从目标queue中获取test case的最小索引和最大索引界限。
之后o变量从n-1开始,不断递减直到m,然后获取每个索引为o的test case

o = n - 1;

while (o >= m) {

    s32         fd;
    struct stat st;

    sprintf(path, "%s/%s", qd_path, namelist[o]->d_name);
    afl->syncing_case = next_min_accept;
    next_min_accept++;
    o--;

    /* Allow this to fail in case the other fuzzer is resuming or so... */

    fd = open(path, O_RDONLY);

    if (fd < 0) { continue; }

    if (fstat(fd, &st)) { WARNF("fstat() failed"); }

    /* Ignore zero-sized or oversized files. */

    if (st.st_size && st.st_size <= MAX_FILE) {
... // 对每个o索引的test case的一系列操作
    }

    close(fd);

}

然后在每一轮对o索引的test case的一系列操作如下

u8  fault;
u8 *mem = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

if (mem == MAP_FAILED) { PFATAL("Unable to mmap '%s'", path); }

/* See what happens. We rely on save_if_interesting() to catch major
    errors and save the test case. */

write_to_testcase(afl, mem, st.st_size);

fault = fuzz_run_target(afl, &afl->fsrv, afl->fsrv.exec_tmout);

if (afl->stop_soon) { goto close_sync; }

afl->syncing_party = sd_ent->d_name;
afl->queued_imported +=
    save_if_interesting(afl, mem, st.st_size, fault);
afl->syncing_party = 0;

munmap(mem, st.st_size);

主要过程为

  • 读入test case
  • 将读入的test case经过write_to_testcase函数,设置到afl状态中对应的buf中,并写出到testcase文件。此处write_to_testcase不一定是将原始的test case直接写入buf中,如果有afl_custom_post_process的话,会调用它对test case进行后续处理再写入buf中,再写出到testcase文件。
  • 跑一次fuzz_run_target看看是否超时
  • 然后经过一次save_if_interesting的测试来执行一次目标程序的运行,如果intestest的话,会把test case加入queue并返回1.即如果interest的话,queued_imported会被设置为1.

最后就是各种清理工作了。

如果整个过程slave并没有找到master的话,它会把自己当作master并设置对应的is_main_node文件。
此处注释中说,如果多个slave同时没有找到master,将自己设置为main的时候可能会有race condition,但这个没有问题。因为在下一次运行的时候,这个slave如果在找到master的时候,他自己临时的is_main_node会被删除。

  // If we are a secondary and no main was found to sync then become the main
  if (unlikely(synced == 0) && likely(entries) &&
      likely(afl->is_secondary_node)) {

    // there is a small race condition here that another secondary runs at the
    // same time. If so, the first temporary main node running again will demote
    // themselves so this is not an issue

    u8 path[PATH_MAX];
    afl->is_main_node = 1;
    sprintf(path, "%s/is_main_node", afl->out_dir);
    int fd = open(path, O_CREAT | O_RDWR, 0644);
    if (fd >= 0) { close(fd); }

  }

你可能感兴趣的:(afl++ parallel分析)