AFL Fuzz QEMU新版适配:深度解析 Patch 细节

本文将深度解析 AFL++ 对 QEMU 的 patch 细节。

AFL对 QEMU 的修改

首先,下面列出 AFL对 QEMU 的修改目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
accel/tcg/cpu-exec.c                          | 1582 +++++++++++++++++
accel/tcg/tcg-runtime.c | 824 +++++++++
accel/tcg/tcg-runtime.h | 28 +
accel/tcg/translate-all.c | 212 +++
accel/tcg/translator.c | 30 +
linux-user/elfload.c | 55 +
linux-user/main.c | 120 ++
linux-user/mips/cpu_loop.c | 15 +
linux-user/mmap.c | 63 +
linux-user/signal.c | 42 +-
linux-user/syscall.c | 52 +-
qemuafl/api.h | 215 +++
qemuafl/asan-giovese-inl.h | 1536 ++++++++++++++++
qemuafl/asan-giovese.h | 155 ++
qemuafl/common.h | 200 +++
qemuafl/cpu-translate.h | 177 ++
qemuafl/imported/afl_hash.h | 74 +
qemuafl/imported/cmplog.h | 106 ++
qemuafl/imported/config.h | 591 ++++++
qemuafl/imported/snapshot-inl.h | 115 ++
qemuafl/imported/types.h | 253 +++
qemuafl/interval-tree/.gitignore | 3 +
qemuafl/interval-tree/COPYING | 20 +
qemuafl/interval-tree/compiler.h | 17 +
qemuafl/interval-tree/interval-tree.inl | 2 +
qemuafl/interval-tree/interval_tree_generic.h | 193 ++
qemuafl/interval-tree/rbtree.h | 108 ++
qemuafl/interval-tree/rbtree.inl | 549 ++++++
qemuafl/interval-tree/rbtree_augmented.h | 245 +++
qemuafl/qasan-qemu.h | 143 ++
qemuafl/qasan.h | 264 +++
qemuafl/qemu-ijon-support.h | 65 +
target/mips/tcg/translate.c | 146 ++
tcg/tcg-op.c | 19 +
tcg/tcg.c | 13 +

不过上面的目录并不完全,只针对了目标架构为 mips 的情况,不同架构的以下文件不会一样:

1
2
linux-user/{arch}/cpu_loop.c
target/{arch}/tcg/translate.c

首先,qemuafl目录下的文件为 AFL 相关的头文件,包含相关全局变量结构体类型声明等等。若需查看 AFL 对 QEMU 进行了哪些修改,可以通过检查该文件代码是否包含qemuafl目录下的头文件。

由于 AFL 的 QEMU 使用的是user-mode,因此应从 linux-user 目录下的代码开始分析。

  1. linux-user/main.c文件的代码,patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
diff --git a/linux-user/main.c b/linux-user/main.c
index 2cd867491b..b0172d86fb 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -68,6 +68,9 @@
#define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT)
#endif

+#include "tcg/tcg-op.h"
+#include "qemuafl/qasan-qemu.h"
+
char *exec_path;
char real_exec_path[PATH_MAX];

@@ -267,6 +270,73 @@ CPUArchState *cpu_copy(CPUArchState *env)
return new_env;
}

+/* A shorthand way to suppress the warnings that you are ignoring the return value of asprintf() */
+static inline void ignore_result(long long int unused_result)
+{
+ (void) unused_result;
+}
+
+/* Get libqasan path. */
+#ifndef AFL_PATH
+ #define AFL_PATH "/usr/local/lib/afl/"
+#endif
+static char *get_libqasan_path(char *own_loc)
+{
+ if (!unlikely(own_loc)) {
+ fprintf(stderr, "BUG: param own_loc is NULL\n");
+ exit(EXIT_FAILURE);
+ }
+
+ char *tmp, *cp = NULL, *rsl, *own_copy;
+
+ tmp = getenv("AFL_PATH");
+ if (tmp) {
+ ignore_result(asprintf(&cp, "%s/libqasan.so", tmp));
+ if (access(cp, X_OK)) {
+ fprintf(stderr, "Unable to find '%s'\n", tmp);
+ exit(EXIT_FAILURE);
+ }
+
+ return cp;
+ }
+
+ own_copy = strdup(own_loc);
+ rsl = strrchr(own_copy, '/');
+ if (rsl) {
+ *rsl = 0;
+
+ ignore_result(asprintf(&cp, "%s/libqasan.so", own_copy));
+ free(own_copy);
+
+ if (!access(cp, X_OK)) { return cp; }
+
+ } else {
+ free(own_copy);
+ }
+
+ if (!access(AFL_PATH "/libqasan.so", X_OK)) {
+ if (cp) { free(cp); }
+
+ return strdup(AFL_PATH "/libqasan.so");
+ }
+
+ /* This is an AFL error message, but since it is in QEMU it can't
+ have all the pretty formatting of AFL without importing
+ a bunch of AFL pieces. */
+ fprintf(stderr, "\n" "" "[-] " ""
+ "Oops, unable to find the 'libqasan.so' binary. The binary must be "
+ "built\n"
+ " separately by following the instructions in "
+ "qemu_mode/libqasan/README.md. "
+ "If you\n"
+ " already have the binary installed, you may need to specify "
+ "AFL_PATH in the\n"
+ " environment.\n");
+
+ fprintf(stderr, "Failed to locate 'libqasan.so'.\n");
+ exit(EXIT_FAILURE);
+}
+
static void handle_arg_help(const char *arg)
{
usage(EXIT_SUCCESS);
@@ -713,6 +783,18 @@ int main(int argc, char **argv, char **envp)
unsigned long max_reserved_va;
bool preserve_argv0;

+ use_qasan = !!getenv("AFL_USE_QASAN");
+
+ if (getenv("QASAN_MAX_CALL_STACK"))
+ qasan_max_call_stack = atoi(getenv("QASAN_MAX_CALL_STACK"));
+ if (getenv("QASAN_SYMBOLIZE"))
+ qasan_symbolize = atoi(getenv("QASAN_SYMBOLIZE"));
+
+#if defined(ASAN_GIOVESE) && !defined(DO_NOT_USE_QASAN)
+ if (use_qasan)
+ asan_giovese_init();
+#endif
+
error_init(argv[0]);
module_call_init(MODULE_INIT_TRACE);
qemu_init_cpu_list();
@@ -733,6 +815,45 @@ int main(int argc, char **argv, char **envp)
(void) envlist_setenv(envlist, *wrk);
}

+ /* Add AFL_PRELOAD for qasan if it is enabled */
+ if(use_qasan) {
+ char *preload = getenv("AFL_PRELOAD");
+ char *libqasan = get_libqasan_path(argv[0]);
+
+ if (!preload) {
+ setenv("AFL_PRELOAD", libqasan, 0);
+ } else {
+ /* NOTE: If there is more than one in the list, LD_PRELOAD allows spaces or colons
+ as separators (but no escaping provided), but DYLD_INSERT_LIBRARIES allows only colons.
+ Prefer colons for maximum compatibility, but use space if the string already has any. */
+ char * afl_preload;
+ if (strchr(preload, ' ')) {
+ ignore_result(asprintf(&afl_preload, "%s %s", libqasan, preload));
+ } else {
+ ignore_result(asprintf(&afl_preload, "%s:%s", libqasan, preload));
+ }
+
+ setenv("AFL_PRELOAD", afl_preload, 1);
+ free(afl_preload);
+ }
+ free(libqasan);
+ }
+
+ /* Expand AFL_PRELOAD to append preload libraries */
+ char *afl_preload = getenv("AFL_PRELOAD");
+ if (afl_preload) {
+ /* NOTE: If there is more than one in the list, LD_PRELOAD allows spaces or colons
+ as separators, but DYLD_INSERT_LIBRARIES allows only colons.
+ Maybe we should attempt to normalize the list here before we assign it? */
+ char * ld_preload;
+ ignore_result(asprintf(&ld_preload, "LD_PRELOAD=%s", afl_preload));
+ envlist_setenv(envlist, ld_preload);
+
+ char * dyld_insert;
+ ignore_result(asprintf(&dyld_insert, "DYLD_INSERT_LIBRARIES=%s", afl_preload));
+ envlist_setenv(envlist, dyld_insert);
+ }
+
/* Read the stack limit from the kernel. If it's "unlimited",
then we can do little else besides use the default. */
{

main.c 代码中添加的内容旨在让 QEMU 支持 QASan。关于 QASan,ChatGPT 的解释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
QASan(QEMU Address Sanitizer)
是 AFL++ 专为 QEMU 模式实现的一种轻量级 Address Sanitizer(内存错误检测)。
在 QEMU 的 linux-user 模式中,通过 LD_PRELOAD + runtime hook 去检测目标程序的内存错误。
QASan 可以检测:
✔️ 堆缓冲区越界(heap OOB)
例如 malloc 100 字节,但写到 100 以外的区域。
✔️ Use-after-free(UAF)
free 之后继续使用。
✔️ Double-free
同一个指针重复释放。
✔️ Invalid free
释放不是 malloc 得到的地址。
✔️ 一些栈溢出触发崩溃行为
(栈上的 shadow memory 不完整,所以能力有限,但比没有强。)
✔️ 内存泄漏检测(部分)
它覆盖的是 heap 和全局区内存错误。
栈检测有限,但也能帮助 fuzzing 提升覆盖率和能检测更多 bug。
  1. linux-user/elfload.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index fa83d78667..e8b1a946fb 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -33,6 +33,8 @@
#include "target/arm/cpu-features.h"
#endif

+#include "qemuafl/common.h"
+
#ifdef _ARCH_PPC64
#undef ARCH_DLINFO
#undef ELF_PLATFORM
@@ -3463,9 +3465,11 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
if (elf_prot & PROT_EXEC) {
if (vaddr < info->start_code) {
info->start_code = vaddr;
+ if (!afl_start_code) afl_start_code = vaddr;
}
if (vaddr_ef > info->end_code) {
info->end_code = vaddr_ef;
+ if (!afl_end_code) afl_end_code = vaddr_ef;
}
}
if (elf_prot & PROT_WRITE) {
@@ -3499,6 +3503,57 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
load_symbols(ehdr, src, load_bias);
}

+ if (getenv("AFL_QEMU_BLOCK_COV")) {
+ block_cov = 1;
+ block_id = 5;
+ }
+
+ if (!afl_exit_point) {
+ char *ptr;
+ if ((ptr = getenv("AFL_EXITPOINT")) != NULL) {
+ afl_exit_point = strtoul(ptr, NULL, 16);
+#ifdef TARGET_ARM
+ /* The least significant bit indicates Thumb mode. */
+ afl_exit_point = afl_exit_point & ~(target_ulong)1;
+#endif
+ if (getenv("AFL_DEBUG") != NULL)
+ fprintf(stderr, "AFL exitpoint: 0x%lx\n",
+ (unsigned long)afl_exit_point);
+ }
+ }
+
+ if (!afl_entry_point) {
+ char *ptr;
+ if ((ptr = getenv("AFL_ENTRYPOINT")) != NULL) {
+ afl_entry_point = strtoul(ptr, NULL, 16);
+ } else {
+ // On PowerPC64 the entry point is the _function descriptor_
+ // of the entry function. For AFL to properly initialize,
+ // afl_entry_point needs to be set to the actual first instruction
+ // as opposed executed by the target program. This as opposed to
+ // where the function's descriptor sits in memory.
+ // copied from PPC init_thread
+#if defined(TARGET_PPC64) && !defined(TARGET_ABI32)
+ if (get_ppc64_abi(info) < 2) {
+ uint64_t val;
+ get_user_u64(val, info->entry);
+ afl_entry_point = val + info->load_bias;
+ } else {
+ afl_entry_point = info->entry;
+ }
+#else
+ afl_entry_point = info->entry;
+#endif
+ }
+#ifdef TARGET_ARM
+ /* The least significant bit indicates Thumb mode. */
+ afl_entry_point = afl_entry_point & ~(target_ulong)1;
+#endif
+ }
+ if (getenv("AFL_DEBUG") != NULL)
+ fprintf(stderr, "AFL forkserver entrypoint: 0x%lx\n",
+ (unsigned long)afl_entry_point);
+
debuginfo_report_elf(image_name, src->fd, load_bias);

mmap_unlock();

elfload.c 文件主要用于读取目标 ELF 文件结构信息。AFL 在该文件中进行全局变量的初始化,比如获取该 ELF 文件的代码段访问。还可以通过AFL_EXITPOINT环境变量设置程序fuzz 的结束地址。还可以使用AFL_ENTRYPOINT环境变量设置fuzz 的入口地址。如果没设置,默认为 ELF 程序的代码起始地址。

  1. linux-user/mmap.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index d1f36e6f16..c080a739bf 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -34,6 +34,25 @@
#include "target/arm/cpu-features.h"
#endif

+#include "qemuafl/common.h"
+#include "qemuafl/interval-tree/interval-tree.inl"
+
+struct mmap_tree_node {
+
+ struct rb_node rb;
+ abi_long start, end;
+ abi_long __subtree_last;
+
+};
+
+#define MMAP_TREE_START(node) ((node)->start)
+#define MMAP_TREE_LAST(node) ((node)->end)
+
+INTERVAL_TREE_DEFINE(struct mmap_tree_node, rb, abi_long, __subtree_last,
+ MMAP_TREE_START, MMAP_TREE_LAST, static, mmap_tree)
+
+static struct rb_root mmap_tree_root = RB_ROOT;
+
static pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER;
static __thread int mmap_lock_count;

@@ -585,6 +604,12 @@ static abi_long mmap_end(abi_ulong start, abi_ulong last,
qemu_log_unlock(f);
}
}
+ if (afl_fork_child && persistent_memory) {
+ struct mmap_tree_node* node = calloc(sizeof(struct mmap_tree_node), 1);
+ node->start = start;
+ node->end = last;
+ mmap_tree_insert(node, &mmap_tree_root);
+ }
return start;
}

@@ -1095,6 +1120,17 @@ int target_munmap(abi_ulong start, abi_ulong len)
if (likely(ret == 0)) {
page_set_flags(start, start + len - 1, 0);
shm_region_rm_complete(start, start + len - 1);
+
+ if (afl_fork_child && persistent_memory) {
+ struct mmap_tree_node* node = mmap_tree_iter_first(&mmap_tree_root,
+ start, start + len - 1);
+ while (node) {
+ struct mmap_tree_node* next = mmap_tree_iter_next(node, start,
+ start + len - 1);
+ mmap_tree_remove(node, &mmap_tree_root);
+ node = next;
+ }
+ }
}
mmap_unlock();

@@ -1189,6 +1225,21 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
page_set_flags(new_addr, new_addr + new_size - 1,
prot | PAGE_VALID | PAGE_RESET);
shm_region_rm_complete(new_addr, new_addr + new_size - 1);
+ if (afl_fork_child && persistent_memory) {
+ struct mmap_tree_node* node = mmap_tree_iter_first(&mmap_tree_root,
+ old_addr, old_addr + old_size - 1);
+ while (node) {
+ struct mmap_tree_node* next = mmap_tree_iter_next(node, old_addr,
+ old_addr + old_size - 1);
+ mmap_tree_remove(node, &mmap_tree_root);
+ node = next;
+ }
+
+ node = calloc(sizeof(struct mmap_tree_node), 1);
+ node->start = new_addr;
+ node->end = new_addr + new_size - 1;
+ mmap_tree_insert(node, &mmap_tree_root);
+ }
}
mmap_unlock();
return new_addr;
@@ -1486,3 +1537,15 @@ abi_long target_shmdt(abi_ulong shmaddr)
}
return rv;
}
+
+void afl_target_unmap_trackeds(void) {
+
+ struct mmap_tree_node* node = mmap_tree_iter_first(&mmap_tree_root, 0,
+ (abi_ulong)-1);
+ while (node) {
+ struct mmap_tree_node* next = mmap_tree_iter_next(node, 0, (abi_ulong)-1);
+ target_munmap(node->start, node->end - node->start);
+ node = next;
+ }
+
+}

mmap.c 文件涉及内存管理。在 persistent 模式下,AFL 需要自动跟踪目标应用的所有 mmap 内存区域。

  1. linux-user/signal.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
diff --git a/linux-user/signal.c b/linux-user/signal.c
index 4dafc2c3a2..8c99829023 100644
--- a/linux-user/signal.c
+++ b/linux-user/signal.c
@@ -39,6 +39,9 @@
#include "user/signal.h"
#include "tcg/tcg.h"

+#include "tcg/tcg-op.h"
+#include "qemuafl/qasan-qemu.h"
+
/* target_siginfo_t must fit in gdbstub's siginfo save area. */
QEMU_BUILD_BUG_ON(sizeof(target_siginfo_t) > MAX_SIGINFO_LENGTH);

@@ -1293,19 +1296,55 @@ static void handle_pending_signal(CPUArchState *cpu_env, int sig,
print_taken_signal(sig, &unswapped);
}

- if (handler == TARGET_SIG_DFL) {
- /* default handler : ignore some signal. The other are job control or fatal */
+ int ignore_handling = !!getenv("AFL_QEMU_FORCE_DFL");
+
+ if (handler == TARGET_SIG_DFL || ignore_handling) {
+ /* default handler : ignore some signal. The other are job control or fatal */
if (sig == TARGET_SIGTSTP || sig == TARGET_SIGTTIN || sig == TARGET_SIGTTOU) {
kill(getpid(),SIGSTOP);
} else if (sig != TARGET_SIGCHLD &&
sig != TARGET_SIGURG &&
sig != TARGET_SIGWINCH &&
sig != TARGET_SIGCONT) {
+#if defined(ASAN_GIOVESE) && !defined(DO_NOT_USE_QASAN)
+ if (use_qasan) {
+ if (sig == TARGET_SIGILL ||
+ sig != TARGET_SIGFPE ||
+ sig != TARGET_SIGSEGV ||
+ sig != TARGET_SIGBUS)
+ asan_giovese_deadly_signal(target_to_host_signal(sig),
+ k->info._sifields._sigfault._addr,
+ PC_GET(cpu_env), BP_GET(cpu_env),
+ SP_GET(cpu_env));
+ else
+ asan_giovese_deadly_signal(target_to_host_signal(sig),
+ PC_GET(cpu_env),
+ PC_GET(cpu_env), BP_GET(cpu_env),
+ SP_GET(cpu_env));
+ }
+#endif
dump_core_and_abort(cpu_env, sig);
}
} else if (handler == TARGET_SIG_IGN) {
/* ignore sig */
} else if (handler == TARGET_SIG_ERR) {
+#if defined(ASAN_GIOVESE) && !defined(DO_NOT_USE_QASAN)
+ if (use_qasan) {
+ if (sig == TARGET_SIGILL ||
+ sig == TARGET_SIGFPE ||
+ sig == TARGET_SIGSEGV ||
+ sig == TARGET_SIGBUS)
+ asan_giovese_deadly_signal(target_to_host_signal(sig),
+ k->info._sifields._sigfault._addr,
+ PC_GET(cpu_env), BP_GET(cpu_env),
+ SP_GET(cpu_env));
+ else
+ asan_giovese_deadly_signal(target_to_host_signal(sig),
+ PC_GET(cpu_env),
+ PC_GET(cpu_env), BP_GET(cpu_env),
+ SP_GET(cpu_env));
+ }
+#endif
dump_core_and_abort(cpu_env, sig);

signal.c 文件主要处理信号相关内容。AFL 主要通过信号来判断目标程序是否 crash,比如:SIGILL, SIGFPE, SIGSEGV, SIGBUS

然而,当目标程序自行编写信号处理函数时,AFL 可能无法捕获相关信号。因此,可通过

除此之外,还有添加跟QASan相关的代码,在 fatal signals 之前调用 QASan。

  1. linux-user/syscall.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 3a25abfaca..d6612ace5a 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -148,6 +148,9 @@
#include "fd-trans.h"
#include "user/cpu_loop.h"

+#include "qemuafl/common.h"
+#include "qemuafl/qasan-qemu.h"
+
#ifndef CLONE_IO
#define CLONE_IO 0x80000000 /* Clone io context */
#endif
@@ -847,6 +850,15 @@ void target_set_brk(abi_ulong new_brk)
initial_target_brk = target_brk;
}

+abi_ulong afl_get_brk(void) {
+ return target_brk;
+}
+abi_ulong afl_set_brk(abi_ulong new_brk) {
+ abi_ulong old_brk = target_brk;
+ target_brk = new_brk;
+ return old_brk;
+}
+
/* do_brk() must return target values and target errnos. */
abi_long do_brk(abi_ulong brk_val)
{
@@ -8590,7 +8602,7 @@ static int do_execv(CPUArchState *cpu_env, int dirfd,
abi_long guest_envp, int flags, bool is_execveat)
{
int ret;
- char **argp, **envp;
+ char **argp = NULL, **envp = NULL;
int argc, envc;
abi_ulong gp;
abi_ulong addr;
@@ -8616,6 +8628,35 @@ static int do_execv(CPUArchState *cpu_env, int dirfd,
if (!addr) {
break;
}
+ /* QASAN: remove preloaded library */
+ if (use_qasan && !getenv("QASAN_PRESERVE_EXECVE")) {
+ /*
+ * If we need to clear the LD_PRELOAD list, run the memory
+ * lock and unlock methods to inspect the contents within
+ * the strings.
+ */
+ abi_long len = target_strlen(gp);
+ if (len < 0) {
+ return -TARGET_EFAULT;
+ }
+ char *env = lock_user(VERIFY_WRITE, gp, (long)(len + 1), 0);
+ if (!env)
+ goto execve_efault;
+ if (!strncmp("LD_PRELOAD=", env, 11)) {
+ char *p, *q, *r;
+ if ((q = r = strstr(env +11, "libqasan.so")) != NULL) {
+ size_t mlen = strlen("libqasan.so");
+ while ((r = strstr(p = r + mlen, "libqasan.so")) != NULL) {
+ while (p < r)
+ *q++ = *p++;
+ }
+ while ((*q++ = *p++) != '\0')
+ continue;
+ }
+
+ }
+ unlock_user(env, gp, (long)(len + 1));
+ }
envc++;
}

@@ -13864,6 +13905,15 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,
return ret;
#endif

+ case QASAN_FAKESYS_NR:
+ /* QASAN syscall */
+ if (use_qasan) {
+ return qasan_actions_dispatcher(cpu_env, arg1, arg2, arg3, arg4);
+ } else {
+ fprintf(stderr, "QAsan syscall unsupported without enabling QASan mode (AFL_USE_QASAN)\n");
+ return -TARGET_ENOSYS;
+ }
+
#if defined(TARGET_NR_pivot_root)

syscall.c 文件主要处理系统调用相关指令。该补丁主要进行了三类修改:

  • 新增 brk 相关的 AFL/QASAN 辅助函数
  • execve 环境变量处理增强:自动移除 LD_PRELOAD 中的 libqasan.so
  • 新增 QASAN 的 “fake syscall” 接口,用于与 QASAN 交互
  1. linux-user/mips/cpu_loop.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c
index 6405806eb0..ce6a620b7f 100644
--- a/linux-user/mips/cpu_loop.c
+++ b/linux-user/mips/cpu_loop.c
@@ -26,6 +26,9 @@
#include "internal.h"
#include "fpu_helper.h"

+/* MIPS_PATCH */
+#include "qemuafl/common.h"
+
# ifdef TARGET_ABI_MIPSO32
# define MIPS_SYSCALL_NUMBER_UNUSED -1
static const int8_t mips_syscall_args[] = {
@@ -78,6 +81,18 @@ void cpu_loop(CPUMIPSState *env)

switch(trapnr) {
case EXCP_SYSCALL:
+ if (
+ persistent_exits &&
+ (
+ env->active_tc.gpr[2] == TARGET_NR_exit_group ||
+ // uclibc may use the following signal instead of
+ // exit_group:
+ env->active_tc.gpr[2] == TARGET_NR_exit
+ )
+ ) {
+ env->active_tc.PC = afl_persistent_addr;
+ continue;
+ }
env->active_tc.PC += 4;
# ifdef TARGET_ABI_MIPSO32
syscall_num = env->active_tc.gpr[2] - 4000;

linux-user/{arch}/cpu_loop.c 文件包含 QEMU 执行客户端代码的主要函数。AFL 在该文件中添加的内容为:在persistent模式下,如果遇到exitexit_group函数,并不会真正的退出,而是跳转到afl_persistent_addr地址。

接下来是accel/tcg目录下的代码,在默认情况下,QEMU 仿真的流程为:

1
Guest CPU 指令  →  TCG IR(中间指令) →  Host 机器码 → 执行

TCG IR 翻译为主机指令的操作即在该目录下完成。

  1. accel/tcg/tcg-runtime.h文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
diff --git a/accel/tcg/tcg-runtime.h b/accel/tcg/tcg-runtime.h
index c23b5e66c4..e71cf0ca52 100644
--- a/accel/tcg/tcg-runtime.h
+++ b/accel/tcg/tcg-runtime.h
@@ -323,3 +323,31 @@ DEF_HELPER_FLAGS_4(gvec_leus32, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)
DEF_HELPER_FLAGS_4(gvec_leus64, TCG_CALL_NO_RWG, void, ptr, ptr, i64, i32)

DEF_HELPER_FLAGS_5(gvec_bitsel, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+
+DEF_HELPER_FLAGS_1(afl_entry_routine, TCG_CALL_NO_RWG, void, env)
+DEF_HELPER_FLAGS_1(afl_persistent_routine, TCG_CALL_NO_RWG, void, env)
+DEF_HELPER_FLAGS_1(afl_maybe_log, TCG_CALL_NO_RWG, void, tl)
+DEF_HELPER_FLAGS_1(afl_maybe_log2, TCG_CALL_NO_RWG, void, tl)
+DEF_HELPER_FLAGS_1(afl_maybe_log_trace, TCG_CALL_NO_RWG, void, tl)
+DEF_HELPER_FLAGS_3(afl_compcov_16, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_3(afl_compcov_32, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_3(afl_compcov_64, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_3(afl_cmplog_8, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_3(afl_cmplog_16, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_3(afl_cmplog_32, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_3(afl_cmplog_64, TCG_CALL_NO_RWG, void, tl, tl, tl)
+DEF_HELPER_FLAGS_1(afl_cmplog_rtn, TCG_CALL_NO_RWG, void, env)
+
+DEF_HELPER_FLAGS_5(qasan_fake_instr, TCG_CALL_NO_RWG, tl, env, tl, tl, tl, tl)
+DEF_HELPER_FLAGS_2(qasan_load1, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_load2, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_load4, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_load8, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_store1, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_store2, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_store4, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_2(qasan_store8, TCG_CALL_NO_RWG, void, env, tl)
+DEF_HELPER_FLAGS_1(qasan_shadow_stack_push, TCG_CALL_NO_RWG, void, tl)
+DEF_HELPER_FLAGS_1(qasan_shadow_stack_pop, TCG_CALL_NO_RWG, void, tl)
+
+DEF_HELPER_FLAGS_4(ijon_func_call, TCG_CALL_NO_RWG, void, tl, tl, tl, tl)

accel/tcg/tcg-runtime.h头文件主要是用来声明 TCG Helper函数,AFL 在该文件中新增了一些 AFL 相关的Helper 函数。

下面介绍 TCG Helper 函数的结构。

1). 在include/exec/helper-proto.h.inc函数中定义了Helper 函数声明的宏。

DEF_HELPER_FLAGS_x为Helper 函数宏,其中 x 标识有几个参数,比如:DEF_HELPER_FLAGS_2表示该函数有两个参数。

helper-proto.h.inc文件中的宏为:

1
2
#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \
dh_ctype(ret) HELPER(name) (dh_ctype(t1), dh_ctype(t2)) DEF_HELPER_ATTR;

若将 DEF_HELPER_FLAGS_2(qasan_load2, TCG_CALL_NO_RWG, void, env, tl) 展开,函数声明为:void helper_qasan_load2(CPUArchState *, i32) __attribute__((noinline));

2). 在include/exec/helper-info.h.inc函数中定义了 Helper 函数相关的结构体宏。

helper-info.h.inc文件中的宏为:

1
2
3
4
5
6
7
#define DEF_HELPER_FLAGS_2(NAME, FLAGS, RET, T1, T2)                    \
TCGHelperInfo glue(helper_info_, NAME) = { \
.func = HELPER(NAME), .name = str(NAME), \
.flags = FLAGS | dh_callflag(RET), \
.typemask = dh_typemask(RET, 0) | dh_typemask(T1, 1) \
| dh_typemask(T2, 2) \
};

把该宏展开,如下所示:

1
2
3
4
5
TCGHelperInfo helper_info_qasan_load2 = {
.func = helper_qasan_load2, .name = qasan_load2,
.flags = TCG_CALL_NO_RWG | dh_callflag_void,
.typemask = ......
}

3). 在include/exec/helper-gen.h.inc函数中定义了 Helper 函数调用函数宏。

helper-gen.h.inc文件中的宏为:

1
2
3
4
5
6
7
8
9
#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2)                    \
extern TCGHelperInfo glue(helper_info_, name); \
static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \
dh_arg_decl(t1, 1), dh_arg_decl(t2, 2)) \
{ \
tcg_gen_call2(glue(helper_info_,name).func, \
&glue(helper_info_,name), dh_retvar(ret), \
dh_arg(t1, 1), dh_arg(t2, 2)); \
}

把该宏展开,如下所示:

1
2
3
4
5
6
7
extern TCGHelperInfo helper_info_qasan_load2;
static inline void gen_helper_qasan_load2(TCGv_ptr arg1, TCGv_i32 arg2)
{
tcg_gen_call2(helper_info_qasan_load2.func,
&helper_info_qasan_load2, NULL,
tcgv_ptr_temp(arg1), tcgv_i32_temp(arg2));
}

最后就是HELPER 函数的实现,如下所示:

1
2
3
4
5
6
7
8
void HELPER(qasan_load2)(CPUArchState *env, target_ulong addr) {
......
}

宏展开后变为:
void helper_qasan_load2(CPUArchState *env, target_ulong addr) {
......
}

当需要调用 helper 函数时,调用的不是helper_qasan_load2(x, x),而是gen_helper_qasan_load2(x, x)

  1. accel/tcg/tcg-runtime.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
diff --git a/accel/tcg/tcg-runtime.c b/accel/tcg/tcg-runtime.c
index fa7ed9739c..56ebdb1261 100644
--- a/accel/tcg/tcg-runtime.c
+++ b/accel/tcg/tcg-runtime.c
@@ -31,6 +31,292 @@
#include "exec/helper-info.c.inc"
#undef HELPER_H

+#include "qemuafl/common.h"
+#include "qemuafl/qemu-ijon-support.h"
+
+uint32_t afl_hash_ip(uint64_t);
+
+void HELPER(ijon_func_call)(target_ulong var_addr, target_ulong var_len, target_ulong itype, target_ulong idx)
+{
+ uint64_t buf = 0;
+ memcpy(&buf, var_addr, var_len);
+ ijon_dispatch(itype, idx, buf);
+ fprintf(stderr, "trigger ijon: addr=0x%016" PRIx64 " tag=%s value %ld\n", var_addr, ijon_to_str(itype), buf);
+}
+
+void HELPER(afl_entry_routine)(CPUArchState *env) {
+
+ afl_forkserver(env_cpu(env));
+
+}
+
...
+
+#include <sys/mman.h>
+#include "linux-user/qemu.h" /* access_ok decls. */
+
+/*
+static int area_is_mapped(void *ptr, size_t len) {
+
+ char *p = ptr;
+ char *page = (char *)((uintptr_t)p & ~(sysconf(_SC_PAGE_SIZE) - 1));
+
+ int r = msync(page, (p - page) + len, MS_ASYNC);
+ if (r < 0) return errno != ENOMEM;
+ return 1;
+
+}
+*/
+
...
+/////////////////////////////////////////////////
+// QASAN
+/////////////////////////////////////////////////
+
+#include "qemuafl/qasan-qemu.h"
+
+// options
+int qasan_max_call_stack = 16; // QASAN_MAX_CALL_STACK
+int qasan_symbolize = 1; // QASAN_SYMBOLIZE
+int use_qasan = 0;
+
+__thread int qasan_disabled;
+
+__thread struct shadow_stack qasan_shadow_stack;
+
+#ifdef ASAN_GIOVESE
+
+#ifndef DO_NOT_USE_QASAN
+
+#include "qemuafl/asan-giovese-inl.h"
+
+#include <sys/types.h>
+#include <sys/syscall.h>
+
...
+
int32_t HELPER(rem_i32)(int32_t arg1, int32_t arg2)

tcg-runtime.c 原本用于实现 TCG Helper 函数。AFL 在该文件中增加了相关 Helper 函数的实现代码。

  1. accel/tcg/translator.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
diff --git a/accel/tcg/translator.c b/accel/tcg/translator.c
index ef1538b4fc..f33048f522 100644
--- a/accel/tcg/translator.c
+++ b/accel/tcg/translator.c
@@ -21,6 +21,8 @@
#include "disas/disas.h"
#include "tb-internal.h"

+#include "qemuafl/common.h"
+
static void set_can_do_io(DisasContextBase *db, bool val)
{
QEMU_BUILD_BUG_ON(sizeof_field(CPUState, neg.can_do_io) != 1);
@@ -167,6 +169,34 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
plugin_gen_insn_start(cpu, db);
}

+ if (db->pc_next == afl_entry_point) {
+ static bool first = true;
+ /*
+ * We guard this section since we flush the translation cache after
+ * we load the configuration, which in turn means we will need to
+ * re-translate our block. If we were to perform this flush every
+ * time (rather than just when our configuration is first loaded),
+ * we would just end up translation this block repeatedly.
+ */
+ if (first) {
+ afl_setup();
+ /*
+ * We flush the translation cache here since we may already have
+ * translated some blocks and included instrumentation in them
+ * before we have processed the configuration from the
+ * environment variables which configures which ranges to
+ * include and exclude. Therefore we may have some blocks in our
+ * cache which are incorrectly instrumented and cause some
+ * fuzzing stability or performance problems.
+ */
+ tb_flush(cpu);
+ first = false;
+ }
+ gen_helper_afl_entry_routine(cpu_env);
+ } else if (db->pc_next == afl_exit_point) {
+ _exit(0);
+ }
+
/*
* Disassemble one instruction. The translate_insn hook should
* update db->pc_next and db->is_jmp to indicate what should be

QEMU 进行 Guest CPU 指令 → TCG IR(中间指令) 翻译的主要流程代码位于 translator.ctranslator_loop 函数中。

AFL 在该函数中添加了入口流程的代码,当翻译的地址为afl_entry_point时,则调用gen_helper_afl_entry_routine函数,而该函数就是 AFL forkserver 模式通信的核心函数。

AFL forkserver 工作模式如下:

AFL 首先 fork 出一个子进程,随后创建两个管道,一个负责输入,一个负责输出。默认情况下,这两个管道的描述符为:FORKSRV_FD, FORKSRV_FD+1

qemuafl/imported/config.h中定义了:#define FORKSRV_FD 198

其中FORKSRV_FD负责 AFL->QEMU 通信,FORKSRV_FD+1负责QEMU->AFL 通信。

创建该管道的子进程使用 execve 执行 QEMU,QEMU 进程将继承这两个管道,从而实现 AFL 和 QEMU 的进程间通信。

gen_helper_afl_entry_routine函数的实现代码为:

1
2
3
void HELPER(afl_entry_routine)(CPUArchState *env) {
afl_forkserver(env_cpu(env));
}

实际上调用的是 afl_forkserver 函数,该函数用于与 AFL 进行握手。若握手失败,则退出并进入原本的 QEMU 流程;若握手成功,则该进程保持与 AFL 通信,并 fork 出一个子进程以继续 QEMU 的后续流程。

由于是 fork 出的子进程,将完全拷贝一份内存数据,且不影响父进程的内存空间结构。AFL 可借此快速执行每次 fuzz 流程。

afl_forkserver 函数中,还会通过 afl_wait_tsl 函数接收子进程指令翻译的情况,并同步至父进程。这样父进程下次 fork 的子进程可直接执行 Host 机器码,无需重复指令翻译流程,从而大幅提升 QEMU 仿真速度。

  1. accel/tcg/translate-all.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c
index a497c54b80..7a6554b730 100644
--- a/accel/tcg/translate-all.c
+++ b/accel/tcg/translate-all.c
@@ -67,6 +67,104 @@
#include "tcg/perf.h"
#include "tcg/insn-start-words.h"

+#include "qemuafl/common.h"
+#include "tcg/tcg-op.h"
+#include "qemuafl/imported/afl_hash.h"
+
+#include <math.h>
+
+__thread int cur_block_is_good;
+
+static int afl_track_unstable_log_fd(void) {
+ static bool initialized = false;
+ static int track_fd = -1;
+ if (unlikely(!initialized)) {
+ char * fname = getenv("AFL_QEMU_TRACK_UNSTABLE");
+ if (fname != NULL) {
+ track_fd = open(fname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR);
+ }
+ initialized = true;
+ if (track_fd > 0) dprintf(track_fd, "QEMU UNSTABLE TRACKING ENABLED\n");
+ }
+ return track_fd;
+}
+
+void HELPER(afl_maybe_log)(target_ulong cur_loc) {
+ register uintptr_t afl_idx = cur_loc ^ afl_prev_loc;
+
+ INC_AFL_AREA(afl_idx);
+
+ // afl_prev_loc = ((cur_loc & (MAP_SIZE - 1) >> 1)) |
+ // ((cur_loc & 1) << ((int)ceil(log2(MAP_SIZE)) -1));
+ afl_prev_loc = cur_loc >> 1;
+}
+
+void HELPER(afl_maybe_log2)(target_ulong cur_loc) {
+ register uintptr_t afl_idx = cur_loc;
+ INC_AFL_AREA(afl_idx);
+}
+
+void HELPER(afl_maybe_log_trace)(target_ulong cur_loc) {
+ register uintptr_t afl_idx = cur_loc;
+ INC_AFL_AREA(afl_idx);
+}
+
+static target_ulong pc_hash(target_ulong x) {
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = ((x >> 16) ^ x) * 0x45d9f3b;
+ x = (x >> 16) ^ x;
+ return x;
+}
+
+/* Generates TCG code for AFL's tracing instrumentation. */
+static void afl_gen_trace(target_ulong cur_loc) {
+
+ /* Optimize for cur_loc > afl_end_code, which is the most likely case on
+ Linux systems. */
+
+ cur_block_is_good = afl_must_instrument(cur_loc);
+
+ if (!cur_block_is_good)
+ return;
+
+ /* Looks like QEMU always maps to fixed locations, so ASLR is not a
+ concern. Phew. But instruction addresses may be aligned. Let's mangle
+ the value to get something quasi-uniform. */
+
+ if (block_cov) {
+
+ cur_loc = block_id;
+ ++block_id;
+ if (block_id >= MAP_SIZE) block_id = 5;
+
+ TCGv cur_loc_v = tcg_const_tl(cur_loc);
+ gen_helper_afl_maybe_log2(cur_loc_v);
+ tcg_temp_free(cur_loc_v);
+
+ } else {
+
+ // cur_loc = (cur_loc >> 4) ^ (cur_loc << 8);
+ // cur_loc &= MAP_SIZE - 1;
+ cur_loc = (uintptr_t)(afl_hash_ip((uint64_t)cur_loc));
+ cur_loc &= (MAP_SIZE - 1);
+
+ /* Implement probabilistic instrumentation by looking at scrambled block
+ address. This keeps the instrumented locations stable across runs. */
+
+ if (cur_loc >= afl_inst_rms) return;
+
+ TCGv cur_loc_v = tcg_const_tl(cur_loc);
+ if (unlikely(afl_track_unstable_log_fd() >= 0)) {
+ gen_helper_afl_maybe_log_trace(cur_loc_v);
+ } else {
+ gen_helper_afl_maybe_log(cur_loc_v);
+ }
+ tcg_temp_free(cur_loc_v);
+
+ }
+
+}
+
TBContext tb_ctx;

/*
@@ -276,6 +374,7 @@ static int setjmp_gen_code(CPUArchState *env, TranslationBlock *tb,
tcg_func_start(tcg_ctx);

CPUState *cs = env_cpu(env);
+ afl_gen_trace(pc);
tcg_ctx->cpu = cs;
cs->cc->tcg_ops->translate_code(cs, tb, max_insns, pc, host_pc);

@@ -283,9 +382,122 @@ static int setjmp_gen_code(CPUArchState *env, TranslationBlock *tb,
tcg_ctx->cpu = NULL;
*max_insns = tb->icount;

+ /* If we are tracking block instability, then since afl-fuzz will log the ids
+ of the unstable blocks, in fuzzer_stats, we must log these alongside the
+ instruction pointer so that the user can associate these back with the
+ actual binary */
+ int track_fd = afl_track_unstable_log_fd();
+ if (unlikely(track_fd >= 0)) {
+ uint64_t ip = (uint64_t)pc;
+ uintptr_t block_id = (uintptr_t)(afl_hash_ip(ip));
+ block_id &= (MAP_SIZE - 1);
+ dprintf(track_fd, "BLOCK ID: 0x%016" PRIx64 ", PC: 0x%016zx-0x%016zx\n",
+ block_id, ip, ip + tb->size);
+ }
+
return tcg_gen_code(tcg_ctx, tb, pc);
}

+/* Called with mmap_lock held for user mode emulation. */
+TranslationBlock *afl_gen_edge(CPUState *cpu, unsigned long afl_id)
+{
+ CPUArchState *env = cpu->env_ptr;
+ TranslationBlock *tb;
+ tcg_insn_unit *gen_code_buf;
+ int gen_code_size, search_size;
+
+ assert_memory_lock();
+
+ buffer_overflow1:
+ tb = tcg_tb_alloc(tcg_ctx);
+ if (unlikely(!tb)) {
+ /* flush must be done */
+ tb_flush(cpu);
+ mmap_unlock();
+ /* Make the execution loop process the flush as soon as possible. */
+ cpu->exception_index = EXCP_INTERRUPT;
+ cpu_loop_exit(cpu);
+ }
+
+ gen_code_buf = tcg_ctx->code_gen_ptr;
+ tb->tc.ptr = gen_code_buf;
+ tb->pc = 0;
+ tb->cs_base = 0;
+ tb->flags = 0;
+ tb->cflags = 0;
+ tb->trace_vcpu_dstate = *cpu->trace_dstate;
+ tcg_ctx->tb_cflags = 0;
+
+ tcg_func_start(tcg_ctx);
+
+ tcg_ctx->cpu = env_cpu(env);
+
+ target_ulong afl_loc = afl_id & (MAP_SIZE -1);
+ //*afl_dynamic_size = MAX(*afl_dynamic_size, afl_loc);
+ TCGv tmp0 = tcg_const_tl(afl_loc);
+ if (block_cov)
+ gen_helper_afl_maybe_log2(tmp0);
+ else
+ gen_helper_afl_maybe_log(tmp0);
+ tcg_temp_free(tmp0);
+ tcg_gen_goto_tb(0);
+ tcg_gen_exit_tb(tb, 0);
+
+ tcg_ctx->cpu = NULL;
+
+ trace_translate_block(tb, tb->pc, tb->tc.ptr);
+
+ /* generate machine code */
+ tb->jmp_reset_offset[0] = TB_JMP_RESET_OFFSET_INVALID;
+ tb->jmp_reset_offset[1] = TB_JMP_RESET_OFFSET_INVALID;
+ tcg_ctx->tb_jmp_reset_offset = tb->jmp_reset_offset;
+ if (TCG_TARGET_HAS_direct_jump) {
+ tcg_ctx->tb_jmp_insn_offset = tb->jmp_target_arg;
+ tcg_ctx->tb_jmp_target_addr = NULL;
+ } else {
+ tcg_ctx->tb_jmp_insn_offset = NULL;
+ tcg_ctx->tb_jmp_target_addr = tb->jmp_target_arg;
+ }
+
+ /* ??? Overflow could be handled better here. In particular, we
+ don't need to re-do gen_intermediate_code, nor should we re-do
+ the tcg optimization currently hidden inside tcg_gen_code. All
+ that should be required is to flush the TBs, allocate a new TB,
+ re-initialize it per above, and re-do the actual code generation. */
+ gen_code_size = tcg_gen_code(tcg_ctx, tb);
+ if (unlikely(gen_code_size < 0)) {
+ goto buffer_overflow1;
+ }
+ search_size = encode_search(tb, (void *)gen_code_buf + gen_code_size);
+ if (unlikely(search_size < 0)) {
+ goto buffer_overflow1;
+ }
+ tb->tc.size = gen_code_size;
+
+ qatomic_set(&tcg_ctx->code_gen_ptr, (void *)
+ ROUND_UP((uintptr_t)gen_code_buf + gen_code_size + search_size,
+ CODE_GEN_ALIGN));
+
+ /* init jump list */
+ qemu_spin_init(&tb->jmp_lock);
+ tb->jmp_list_head = (uintptr_t)NULL;
+ tb->jmp_list_next[0] = (uintptr_t)NULL;
+ tb->jmp_list_next[1] = (uintptr_t)NULL;
+ tb->jmp_dest[0] = (uintptr_t)NULL;
+ tb->jmp_dest[1] = (uintptr_t)NULL;
+
+ /* init original jump addresses which have been set during tcg_gen_code() */
+ if (tb->jmp_reset_offset[0] != TB_JMP_RESET_OFFSET_INVALID) {
+ tb_reset_jump(tb, 0);
+ }
+ if (tb->jmp_reset_offset[1] != TB_JMP_RESET_OFFSET_INVALID) {
+ tb_reset_jump(tb, 1);
+ }
+
+ return tb;
+}
+
+
/* Called with mmap_lock held for user mode emulation. */
TranslationBlock *tb_gen_code(CPUState *cpu,

translate-all.c 文件主要用于将 TCG IR 翻译成 Host 机器码

该 patch 将 AFL 的 qemu-mode 插桩深度整合进 TCG 代码,使 QEMU 在翻译TB(TCG Block,存储着 TCG IR)时自动生成各种覆盖记录,并添加了 edge TB、生存期概率插桩、不稳定 block 追踪等 AFL 功能。

  1. accel/tcg/cpu-exec.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c
index ef3d967e3a..50435b8135 100644
--- a/accel/tcg/cpu-exec.c
+++ b/accel/tcg/cpu-exec.c
@@ -45,6 +45,1581 @@
#include "internal-common.h"
#include "internal-target.h"

+#include "qemuafl/common.h"
+#include "qemuafl/imported/snapshot-inl.h"
+#include "qemuafl/qemu-ijon-support.h"
+
+#include <string.h>
+#include <sys/shm.h>
+#ifndef AFL_QEMU_STATIC_BUILD
+ #include <dlfcn.h>
+#endif
+
+/***************************
+ * VARIOUS AUXILIARY STUFF *
+ ***************************/
+
+/* This is equivalent to afl-as.h: */
+
+static unsigned char
+ dummy[MAP_SIZE]; /* costs MAP_SIZE but saves a few instructions */
+unsigned char *afl_area_ptr = dummy; /* Exported for afl_gen_trace */
+
+/* Exported variables populated by the code patched into elfload.c: */
+
+abi_ulong afl_entry_point, /* ELF entry point (_start) */
+ afl_exit_point, /* ELF exit point */
+ afl_start_code, /* .text start pointer */
+ afl_end_code; /* .text end pointer */
+
+struct vmrange* afl_instr_code;
+
+abi_ulong afl_persistent_addr, afl_persistent_ret_addr;
+unsigned int afl_persistent_cnt;
+
+unsigned int block_id = 5;
+
+u8 afl_compcov_level, block_cov;
+
+__thread abi_ulong afl_prev_loc;
+
+struct cmp_map *__afl_cmp_map;
+
+/* Set in the child process in forkserver mode: */
+
+static int forkserver_installed = 0;
+static int disable_caching = 0;
+
+unsigned char afl_fork_child;
+unsigned int afl_forksrv_pid;
+unsigned char is_persistent;
+target_long persistent_stack_offset;
+unsigned char persistent_first_pass = 1;
+unsigned char persistent_exits;
+unsigned char persistent_save_gpr;
+unsigned char persistent_memory;
+int persisent_retaddr_offset;
+
+struct api_regs saved_regs;
+
+u8 * shared_buf;
+u32 *shared_buf_len;
+u8 sharedmem_fuzzing;
+
+afl_persistent_hook_fn afl_persistent_hook_ptr;
+
+/* Instrumentation ratio: */
+
+unsigned int afl_inst_rms = MAP_SIZE; /* Exported for afl_gen_trace */
+
+/* Function declarations. */
+
+static void afl_wait_tsl(CPUState *, int);
+static void afl_request_tsl(target_ulong, target_ulong, uint32_t, uint32_t,
+ TranslationBlock *, int);
+
+/* Data structures passed around by the translate handlers: */
+
+struct afl_tb {
+
+ target_ulong pc;
+ target_ulong cs_base;
+ uint32_t flags;
+ uint32_t cf_mask;
+
+};
+
+struct afl_chain {
+
+ struct afl_tb last_tb;
+ uint32_t cf_mask;
+ int tb_exit;
+
+};
+
+struct afl_tsl {
+
+ struct afl_tb tb;
+ struct afl_chain chain;
+ char is_chain;
+
+};
+
+/* Some forward decls: */
+
+static inline TranslationBlock *tb_find(CPUState *, TranslationBlock *, int,
+ uint32_t);
+static inline void tb_add_jump(TranslationBlock *tb, int n,
+ TranslationBlock *tb_next);
+static void afl_map_shm_fuzz(void);
+
+/*************************
+ * ACTUAL IMPLEMENTATION *
+ *************************/
+
+/* Snapshot memory */
+
+struct saved_region {
+
+ void* addr;
+ size_t size;
+ void* saved;
+
+};
+
+abi_ulong saved_brk;
+int lkm_snapshot;
+struct saved_region* memory_snapshot;
+size_t memory_snapshot_len;
+
...
+void afl_setup(void) {
+
+ char *id_str = getenv(SHM_ENV_VAR), *inst_r = getenv("AFL_INST_RATIO");
+
+ int shm_id;
+
+ if (inst_r) {
+
+ unsigned int r;
+
+ r = atoi(inst_r);
+
+ if (r > 100) r = 100;
+ if (!r) r = 1;
+
+ afl_inst_rms = MAP_SIZE * r / 100;
+
+ }
+
+ if (id_str) {
+
+ shm_id = atoi(id_str);
+ afl_area_ptr = shmat(shm_id, NULL, 0);
+
+ if (afl_area_ptr == (void *)-1) exit(1);
+
+ /* With AFL_INST_RATIO set to a low value, we want to touch the bitmap
+ so that the parent doesn't give up on us. */
+
+ if (inst_r) afl_area_ptr[0] = 1;
+
+ }
+
+ disable_caching = getenv("AFL_QEMU_DISABLE_CACHE") != NULL;
+
+ if (getenv("___AFL_EINS_ZWEI_POLIZEI___")) { // CmpLog forkserver
+
+ id_str = getenv(CMPLOG_SHM_ENV_VAR);
+
+ if (id_str) {
+
+ u32 shm_id = atoi(id_str);
+
+ __afl_cmp_map = shmat(shm_id, NULL, 0);
+
+ if (__afl_cmp_map == (void *)-1) exit(1);
+
+ }
+
+ }
+
+ if (getenv("AFL_INST_LIBS")) {
+
+ afl_start_code = 0;
+ afl_end_code = (abi_ulong)-1;
+
+ }
+
+ if (getenv("AFL_CODE_START"))
+ afl_start_code = strtoll(getenv("AFL_CODE_START"), NULL, 16);
+ if (getenv("AFL_CODE_END"))
+ afl_end_code = strtoll(getenv("AFL_CODE_END"), NULL, 16);
+
+ int have_names = 0;
+ if (getenv("AFL_QEMU_INST_RANGES")) {
+ char *str = getenv("AFL_QEMU_INST_RANGES");
+ char *saveptr1, *saveptr2 = NULL, *save_pt1 = NULL;
+ char *pt1, *pt2, *pt3 = NULL;
+
+ while (1) {
+
+ pt1 = strtok_r(str, ",", &saveptr1);
+ if (pt1 == NULL) break;
+ str = NULL;
+ save_pt1 = strdup(pt1);
+
+ pt2 = strtok_r(pt1, "-", &saveptr2);
+ pt3 = strtok_r(NULL, "-", &saveptr2);
+
+ struct vmrange* n = calloc(1, sizeof(struct vmrange));
+ n->next = afl_instr_code;
+
+ if (pt3 == NULL) { // filename
+ have_names = 1;
+ n->start = (target_ulong)-1;
+ n->end = 0;
+ n->name = save_pt1;
+ } else {
+ n->start = strtoull(pt2, NULL, 16);
+ n->end = strtoull(pt3, NULL, 16);
+ if (n->start && n->end) {
+ n->name = NULL;
+ free(save_pt1);
+ } else {
+ have_names = 1;
+ n->start = (target_ulong)-1;
+ n->end = 0;
+ n->name = save_pt1;
+ }
+ }
+
+ afl_instr_code = n;
+
+ }
+ }
+
+ if (getenv("AFL_QEMU_EXCLUDE_RANGES")) {
+ char *str = getenv("AFL_QEMU_EXCLUDE_RANGES");
+ char *saveptr1, *saveptr2 = NULL, *save_pt1;
+ char *pt1, *pt2, *pt3 = NULL;
+
+ while (1) {
+
+ pt1 = strtok_r(str, ",", &saveptr1);
+ if (pt1 == NULL) break;
+ str = NULL;
+ save_pt1 = strdup(pt1);
+
+ pt2 = strtok_r(pt1, "-", &saveptr2);
+ pt3 = strtok_r(NULL, "-", &saveptr2);
+
+ struct vmrange* n = calloc(1, sizeof(struct vmrange));
+ n->exclude = true; // These are "exclusion" regions.
+ n->next = afl_instr_code;
+
+ if (pt3 == NULL) { // filename
+ have_names = 1;
+ n->start = (target_ulong)-1;
+ n->end = 0;
+ n->name = save_pt1;
+ } else {
+ n->start = strtoull(pt2, NULL, 16);
+ n->end = strtoull(pt3, NULL, 16);
+ if (n->start && n->end) {
+ n->name = NULL;
+ free(save_pt1);
+ } else {
+ have_names = 1;
+ n->start = (target_ulong)-1;
+ n->end = 0;
+ n->name = save_pt1;
+ }
+ }
+
+ afl_instr_code = n;
+
+ }
+ }
+
+ if (have_names) {
+ GSList *map_info = read_self_maps();
+ for (GSList *s = map_info; s; s = g_slist_next(s)) {
+ MapInfo *e = (MapInfo *) s->data;
+
+ if (h2g_valid(e->start)) {
+ unsigned long min = e->start;
+ unsigned long max = e->end;
+ int flags = page_get_flags(h2g(min));
+
+ max = h2g_valid(max - 1) ? max : (uintptr_t) AFL_G2H(GUEST_ADDR_MAX) + 1;
+
+ if (page_check_range(h2g(min), max - min, flags) == -1) {
+ continue;
+ }
+
+ // Now that we have a valid guest address region, compare its
+ // name against the names we care about:
+ target_ulong gmin = h2g(min);
+ target_ulong gmax = h2g(max);
+
+ struct vmrange* n = afl_instr_code;
+ while (n) {
+ if (n->name && strstr(e->path, n->name)) {
+ if (gmin < n->start) n->start = gmin;
+ if (gmax > n->end) n->end = gmax;
+ break;
+ }
+ n = n->next;
+ }
+ }
+ }
+ free_self_maps(map_info);
+ }
+
+ if (getenv("AFL_DEBUG") && afl_instr_code) {
+ struct vmrange* n = afl_instr_code;
+ while (n) {
+ if (n->exclude) {
+ fprintf(stderr, "Exclude range: 0x%lx-0x%lx (%s)\n",
+ (unsigned long)n->start, (unsigned long)n->end,
+ n->name ? n->name : "<noname>");
+ } else {
+ fprintf(stderr, "Instrument range: 0x%lx-0x%lx (%s)\n",
+ (unsigned long)n->start, (unsigned long)n->end,
+ n->name ? n->name : "<noname>");
+ }
+ n = n->next;
+ }
+ }
+
+ /* Maintain for compatibility */
+ if (getenv("AFL_QEMU_COMPCOV")) { afl_compcov_level = 1; }
+ if (getenv("AFL_COMPCOV_LEVEL")) {
+
+ afl_compcov_level = atoi(getenv("AFL_COMPCOV_LEVEL"));
+
+ }
+
+ /* pthread_atfork() seems somewhat broken in util/rcu.c, and I'm
+ not entirely sure what is the cause. This disables that
+ behaviour, and seems to work alright? */
+
+ rcu_disable_atfork();
+
+ if (getenv("AFL_QEMU_PERSISTENT_HOOK")) {
+
+#ifdef AFL_QEMU_STATIC_BUILD
+
+ fprintf(stderr,
+ "[AFL] ERROR: you cannot use AFL_QEMU_PERSISTENT_HOOK when "
+ "afl-qemu-trace is static\n");
+ exit(1);
+
+#else
+
+ persistent_save_gpr = 1;
+
+ void *plib = dlopen(getenv("AFL_QEMU_PERSISTENT_HOOK"), RTLD_NOW);
+ if (!plib) {
+
+ fprintf(stderr, "[AFL] ERROR: invalid AFL_QEMU_PERSISTENT_HOOK=%s - %s\n",
+ getenv("AFL_QEMU_PERSISTENT_HOOK"),
+ dlerror());
+ exit(1);
+
+ }
+
+ int (*afl_persistent_hook_init_ptr)(void) =
+ dlsym(plib, "afl_persistent_hook_init");
+ if (afl_persistent_hook_init_ptr)
+ sharedmem_fuzzing = afl_persistent_hook_init_ptr();
+
+ afl_persistent_hook_ptr = dlsym(plib, "afl_persistent_hook");
+ if (!afl_persistent_hook_ptr) {
+
+ fprintf(stderr,
+ "[AFL] ERROR: failed to find the function "
+ "\"afl_persistent_hook\" in %s\n",
+ getenv("AFL_QEMU_PERSISTENT_HOOK"));
+ exit(1);
+
+ }
+
+#endif
+
+ }
+
+ if (__afl_cmp_map) return; // no persistent for cmplog
+
+ is_persistent = getenv("AFL_QEMU_PERSISTENT_ADDR") != NULL;
+
+ if (is_persistent)
+ afl_persistent_addr = strtoll(getenv("AFL_QEMU_PERSISTENT_ADDR"), NULL, 0);
+
+ if (getenv("AFL_QEMU_PERSISTENT_RET"))
+ afl_persistent_ret_addr =
+ strtoll(getenv("AFL_QEMU_PERSISTENT_RET"), NULL, 0);
+ /* If AFL_QEMU_PERSISTENT_RET is not specified patch the return addr */
+
+ if (getenv("AFL_QEMU_PERSISTENT_GPR")) persistent_save_gpr = 1;
+ if (getenv("AFL_QEMU_PERSISTENT_MEM"))
+ persistent_memory = 1;
+
+ if (getenv("AFL_QEMU_PERSISTENT_RETADDR_OFFSET"))
+ persisent_retaddr_offset =
+ strtoll(getenv("AFL_QEMU_PERSISTENT_RETADDR_OFFSET"), NULL, 0);
+
+ if (getenv("AFL_QEMU_PERSISTENT_CNT"))
+ afl_persistent_cnt = strtoll(getenv("AFL_QEMU_PERSISTENT_CNT"), NULL, 0);
+ else
+ afl_persistent_cnt = 0;
+
+ if (getenv("AFL_QEMU_PERSISTENT_EXITS")) persistent_exits = 1;
+
+ // TODO persistent exits for other archs not x86
+ // TODO persistent mode for other archs not x86
+ // TODO cmplog rtn for arm
+
+ if (getenv("AFL_QEMU_SNAPSHOT")) {
+
+ is_persistent = 1;
+ persistent_save_gpr = 1;
+ persistent_memory = 1;
+ persistent_exits = 1;
+
+ if (afl_persistent_addr == 0)
+ afl_persistent_addr = strtoll(getenv("AFL_QEMU_SNAPSHOT"), NULL, 0);
+
+ }
+
+ if (persistent_memory && afl_snapshot_init() >= 0)
+ lkm_snapshot = 1;
+
+ if (getenv("AFL_DEBUG")) {
+ if (is_persistent)
+ fprintf(stderr, "Persistent: 0x%lx [0x%lx] %s%s%s\n",
+ (unsigned long)afl_persistent_addr,
+ (unsigned long)afl_persistent_ret_addr,
+ (persistent_save_gpr ? "gpr ": ""),
+ (persistent_memory ? "mem ": ""),
+ (persistent_exits ? "exits ": ""));
+ }
+
+ qemu_ijon_init();
+
+}
+
+/* Fork server logic, invoked once we hit _start. */
+
+void afl_forkserver(CPUState *cpu) {
+
+ if (forkserver_installed == 1) return;
+ forkserver_installed = 1;
+
+ if (getenv("AFL_QEMU_DEBUG_MAPS")) open_self_maps(cpu->env_ptr, 1);
+
+ u32 __afl_old_forkserver = 0;
+ pid_t child_pid;
+ int t_fd[2];
+ u8 child_stopped = 0;
+ u32 was_killed;
+ u32 version = 0x41464c00 + FS_NEW_VERSION_MAX;
+ u32 tmp = version ^ 0xffffffff, status2, status = version;
+ u8 *msg = (u8 *)&status;
+ u8 *reply = (u8 *)&status2;
+
+ if (getenv("AFL_DEBUG"))
+ fprintf(stderr, "Debug: Sending status 0x%08x\n", status);
+
+ if (getenv("AFL_OLD_FORKSERVER")) {
+
+ __afl_old_forkserver = 1;
+ status = 0;
+
+ fprintf(stderr, "The current version of afl++ qemu mode "
+ "supports forkserver v1, but afl-fuzz still retains "
+ "support for the old forkserver (qemu) version\n");
+
+ }
+
+ /* Tell the parent that we're alive. If the parent doesn't want
+ to talk, assume that we're not running in forkserver mode. */
+
+ if (write(FORKSRV_FD + 1, msg, 4) != 4) return;
+
+ afl_forksrv_pid = getpid();
+
+ int first_run = 1;
+
+ if (!__afl_old_forkserver) {
+
+ if (read(FORKSRV_FD, reply, 4) != 4) { _exit(1); }
+ if (tmp != status2) {
+
+ fprintf(stderr, "wrong forkserver message from AFL++ tool");
+ _exit(1);
+
+ }
+
+ // send the set/requested options to forkserver
+ status = FS_NEW_OPT_MAPSIZE; // we always send the map size
+ if (lkm_snapshot) status |= FS_OPT_SNAPSHOT;
+ if (sharedmem_fuzzing) status |= FS_NEW_OPT_SHDMEM_FUZZ;
+
+ u32 __afl_map_size = MAP_SIZE;
+
+ if (use_ijon) {
+
+ __afl_map_size = (((__afl_map_size + 63) >> 6) << 6);
+ __afl_map_size += MAP_SIZE_IJON_MAP + MAP_SIZE_IJON_BYTES;
+
+ ijon_map_ptr = afl_area_ptr + MAP_SIZE;
+ ijon_max_ptr = (uint64_t*)(ijon_map_ptr + MAP_SIZE_IJON_MAP);
+
+ status |= FS_OPT_IJON;
+
+ }
+
+ if (write(FORKSRV_FD + 1, msg, 4) != 4) {
+
+ errno = 0;
+ _exit(1);
+
+ }
+
+ // Now send the parameters for the set options, increasing by option number
+
+ // FS_NEW_OPT_MAPSIZE - we always send the map size
+ status = __afl_map_size;
+ if (write(FORKSRV_FD + 1, msg, 4) != 4) { _exit(1); }
+
+ // send welcome message as final message
+ status = version;
+ if (write(FORKSRV_FD + 1, msg, 4) != 4) { _exit(1); }
+
+ }
+
+ // END forkserver handshake
+
+ if (sharedmem_fuzzing) { afl_map_shm_fuzz(); }
+
+ /* All right, let's await orders... */
+
+ while (1) {
+
+ /* Whoops, parent dead? */
+
+ if (read(FORKSRV_FD, &was_killed, 4) != 4) exit(2);
+
+ /* If we stopped the child in persistent mode, but there was a race
+ condition and afl-fuzz already issued SIGKILL, write off the old
+ process. */
+
+ if (child_stopped && was_killed) {
+
+ child_stopped = 0;
+ if (waitpid(child_pid, &status, 0) < 0) exit(8);
+
+ }
+
+ if (!child_stopped) {
+
+ /* Establish a channel with child to grab translation commands. We'll
+ read from t_fd[0], child will write to TSL_FD. */
+
+ if (pipe(t_fd) || dup2(t_fd[1], TSL_FD) < 0) exit(3);
+ close(t_fd[1]);
+
+ child_pid = fork();
+ if (child_pid < 0) exit(4);
+
+ if (!child_pid) {
+
+ /* Child process. Close descriptors and run free. */
+
+ afl_fork_child = 1;
+ close(FORKSRV_FD);
+ close(FORKSRV_FD + 1);
+ close(t_fd[0]);
+ return;
+
+ }
+
+ /* Parent. */
+
+ close(TSL_FD);
+
+ } else {
+
+ /* Special handling for persistent mode: if the child is alive but
+ currently stopped, simply restart it with SIGCONT. */
+
+ kill(child_pid, SIGCONT);
+ child_stopped = 0;
+
+ }
+
+ /* Parent. */
+
+ if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) exit(5);
+
+ /* Collect translation requests until child dies and closes the pipe. */
+
+ afl_wait_tsl(cpu, t_fd[0]);
+
+ /* Get and relay exit status to parent. */
+
+ if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0) exit(6);
+
+ /* In persistent mode, the child stops itself with SIGSTOP to indicate
+ a successful run. In this case, we want to wake it up without forking
+ again. */
+
+ if (WIFSTOPPED(status))
+ child_stopped = 1;
+ else if (unlikely(first_run && is_persistent)) {
+
+ fprintf(stderr, "[AFL] ERROR: no persistent iteration executed\n");
+ exit(12); // Persistent is wrong
+
+ }
+
+ first_run = 0;
+
+ if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(7);
+
+ }
+
+}
+
+/* A simplified persistent mode handler, used as explained in
+ * llvm_mode/README.md. */
+
+static u32 cycle_cnt;
+
+void afl_persistent_iter(CPUArchState *env) {
+
+ static struct afl_tsl exit_cmd_tsl;
+
+ if (!afl_persistent_cnt || --cycle_cnt) {
+
+ if (persistent_memory) restore_memory_snapshot();
+
+ if (persistent_save_gpr && !afl_persistent_hook_ptr) {
+ afl_restore_regs(&saved_regs, env);
+ }
+
+ if (!disable_caching) {
+
+ memset(&exit_cmd_tsl, 0, sizeof(struct afl_tsl));
+ exit_cmd_tsl.tb.pc = (target_ulong)(-1);
+
+ if (write(TSL_FD, &exit_cmd_tsl, sizeof(struct afl_tsl)) !=
+ sizeof(struct afl_tsl)) {
+
+ /* Exit the persistent loop on pipe error */
+ afl_area_ptr = dummy;
+ exit(0);
+
+ }
+
+ }
+
+ // TODO use only pipe
+ raise(SIGSTOP);
+
+
+ // now we have shared_buf updated and ready to use
+ if (persistent_save_gpr && afl_persistent_hook_ptr) {
+
+ struct api_regs hook_regs = saved_regs;
+ afl_persistent_hook_ptr(&hook_regs, guest_base, shared_buf,
+ *shared_buf_len);
+ afl_restore_regs(&hook_regs, env);
+
+ }
+
+ afl_area_ptr[0] = 1;
+ afl_prev_loc = 0;
+
+ } else {
+
+ afl_area_ptr = dummy;
+ exit(0);
+
+ }
+
+}
+
+void afl_persistent_loop(CPUArchState *env) {
+
+ if (!afl_fork_child) return;
+
+ if (persistent_first_pass) {
+
+ /* Make sure that every iteration of __AFL_LOOP() starts with a clean slate.
+ On subsequent calls, the parent will take care of that, but on the first
+ iteration, it's our job to erase any trace of whatever happened
+ before the loop. */
+
+ if (is_persistent) {
+
+ memset(afl_area_ptr, 0, MAP_SIZE);
+ afl_area_ptr[0] = 1;
+ afl_prev_loc = 0;
+
+ }
+
+ if (persistent_memory) collect_memory_snapshot();
+
+ if (persistent_save_gpr) {
+
+ afl_save_regs(&saved_regs, env);
+
+ if (afl_persistent_hook_ptr) {
+
+ struct api_regs hook_regs = saved_regs;
+ afl_persistent_hook_ptr(&hook_regs, guest_base, shared_buf,
+ *shared_buf_len);
+ afl_restore_regs(&hook_regs, env);
+
+ }
+
+ }
+
+ cycle_cnt = afl_persistent_cnt;
+ persistent_first_pass = 0;
+ persistent_stack_offset = TARGET_LONG_BITS / 8;
+
+ return;
+
+ }
+
+ if (is_persistent) {
+
+ afl_persistent_iter(env);
+
+ }
+
+}
+
+/* This code is invoked whenever QEMU decides that it doesn't have a
+ translation of a particular block and needs to compute it, or when it
+ decides to chain two TBs together. When this happens, we tell the parent to
+ mirror the operation, so that the next fork() has a cached copy. */
+
+static void afl_request_tsl(target_ulong pc, target_ulong cb, uint32_t flags,
+ uint32_t cf_mask, TranslationBlock *last_tb,
+ int tb_exit) {
+
+ if (disable_caching) return;
+
+ struct afl_tsl t;
+
+ if (!afl_fork_child) return;
+
+ t.tb.pc = pc;
+ t.tb.cs_base = cb;
+ t.tb.flags = flags;
+ t.tb.cf_mask = cf_mask;
+ t.is_chain = (last_tb != NULL);
+
+ if (t.is_chain) {
+
+ t.chain.last_tb.pc = last_tb->pc;
+ t.chain.last_tb.cs_base = last_tb->cs_base;
+ t.chain.last_tb.flags = last_tb->flags;
+ t.chain.cf_mask = cf_mask;
+ t.chain.tb_exit = tb_exit;
+
+ }
+
+ if (write(TSL_FD, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl))
+ return;
+
+}
+
+static inline TranslationBlock *
+afl_tb_lookup(CPUState *cpu, target_ulong pc, target_ulong cs_base,
+ uint32_t flags, uint32_t cf_mask)
+{
+ TranslationBlock *tb;
+ uint32_t hash;
+
+ hash = tb_jmp_cache_hash_func(pc);
+ tb = qatomic_rcu_read(&cpu->tb_jmp_cache[hash]);
+
+ cf_mask &= ~CF_CLUSTER_MASK;
+ cf_mask |= cpu->cluster_index << CF_CLUSTER_SHIFT;
+
+ if (likely(tb &&
+ tb->pc == pc &&
+ tb->cs_base == cs_base &&
+ tb->flags == flags &&
+ tb->trace_vcpu_dstate == *cpu->trace_dstate &&
+ (tb_cflags(tb) & (CF_HASH_MASK | CF_INVALID)) == cf_mask)) {
+ return tb;
+ }
+ tb = tb_htable_lookup(cpu, pc, cs_base, flags, cf_mask);
+ if (tb == NULL) {
+ return NULL;
+ }
+ qatomic_set(&cpu->tb_jmp_cache[hash], tb);
+ return tb;
+}
+
+/* This is the other side of the same channel. Since timeouts are handled by
+ afl-fuzz simply killing the child, we can just wait until the pipe breaks. */
+
+static void afl_wait_tsl(CPUState *cpu, int fd) {
+
+ struct afl_tsl t;
+ TranslationBlock *tb, *last_tb;
+
+ if (disable_caching) return;
+
+ while (1) {
+
+ u8 invalid_pc = 0;
+
+ /* Broken pipe means it's time to return to the fork server routine. */
+
+ if (read(fd, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl)) break;
+
+ /* Exit command for persistent */
+
+ if (t.tb.pc == (target_ulong)(-1)) return;
+
+ tb = afl_tb_lookup(cpu, t.tb.pc, t.tb.cs_base, t.tb.flags, t.tb.cf_mask);
+
+ if (!tb) {
+
+ /* The child may request to transate a block of memory that is not
+ mapped in the parent (e.g. jitted code or dlopened code).
+ This causes a SIGSEV in gen_intermediate_code() and associated
+ subroutines. We simply avoid caching of such blocks. */
+
+ if (is_valid_addr(t.tb.pc)) {
+
+ mmap_lock();
+ tb = tb_gen_code(cpu, t.tb.pc, t.tb.cs_base, t.tb.flags, t.tb.cf_mask);
+ mmap_unlock();
+
+ } else {
+
+ invalid_pc = 1;
+
+ }
+
+ }
+
+ if (t.is_chain && !invalid_pc) {
+
+ last_tb = afl_tb_lookup(cpu, t.chain.last_tb.pc,
+ t.chain.last_tb.cs_base,
+ t.chain.last_tb.flags,
+ t.chain.cf_mask);
+#define TB_JMP_RESET_OFFSET_INVALID 0xffff
+ if (last_tb && (last_tb->jmp_reset_offset[t.chain.tb_exit] !=
+ TB_JMP_RESET_OFFSET_INVALID)) {
+
+ tb_add_jump(last_tb, t.chain.tb_exit, tb);
+
+ }
+
+ }
+
+ }
+
+ close(fd);
+
+}
+
/* -icount align implementation. */

typedef struct SyncClocks {
@@ -946,6 +2521,7 @@ static int __attribute__((noinline))
cpu_exec_loop(CPUState *cpu, SyncClocks *sc)
{
int ret;
+ bool was_translated = false, was_chained = false;

/* if an exception is pending, we execute it here */
while (!cpu_handle_exception(cpu, &ret)) {
@@ -985,6 +2561,7 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc)

mmap_lock();
tb = tb_gen_code(cpu, pc, cs_base, flags, cflags);
+ was_translated = true;
mmap_unlock();

/*
@@ -1011,6 +2588,11 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc)
/* See if we can patch the calling TB. */
if (last_tb) {
tb_add_jump(last_tb, tb_exit, tb);
+ was_chained = true;
+ }
+ if (was_translated || was_chained) {
+ afl_request_tsl(s.pc, s.cs_base, s.flags, s.cf_mask,
+ was_chained ? last_tb : NULL, tb_exit);
}

cpu_loop_exec_tb(cpu, tb, pc, &last_tb, &tb_exit);

cpu-exec.c 中的 cpu_exec_loop 函数为 QEMU 仿真的核心函数,调用流程大致如下:

  • linux-user/main.c 的 main 函数初始化结束后,调用cpu_loop,开始执行目标程序代码。
  • cpu_loop 函数位于linux-user/{arch}/cpu_loop.c 文件中。
  • cpu_loop调用accel/tcg/cpu-exec.c 文件中的cpu_exec函数,进行一些初始化后,执行到cpu_exec_setjmp函数。
  • cpu_exec_setjmp 函数的作用相当于使用 try … catch … 执行 cpu_exec_loop 函数。
  • cpu_exec_loop 函数执行代码块中的 Host 机器码,若不存在则开始翻译。
  • 在cpu_exec_loop 函数中,调用tb_lookup根据当前 pc 地址去搜索对应的代码块。如果存在,则直接调用cpu_loop_exec_tb去执行 Host 机器码。
  • 如果不存在对应的代码块,则调用tb_gen_code开始翻译。
  • 在tb_gen_code中调用到setjmp_gen_code函数。
  • 在setjmp_gen_code函数中,首先使用translate_code函数将Guest 指令翻译成 TCG IR。
  • translate_code函数位于target/{arch}/tcg/cpu.c 文件中。
  • 在翻译成 IR 指令后,再调用tcg_gen_code函数,把 IR 指令翻译成 Host 机器码。

AFL 在该文件中的 patch 是将IJON、snapshot、persistent 模式等功能集成进 QEMU 当中,如下所示:

加入覆盖率共享内存(SHM)映射
加入 AFL forkserver 支持
加入持久化模式(persistent mode)支持
加入持久化模式下 snapshot(快照)/ restore(恢复)功能
加入 IJON(高级指导型 fuzzing)支持
加入比较覆盖(CmpCov)支持
加入 AFL TB 链跟踪结构(afl_tb, afl_chain 等)
加入 sharedmem fuzzing 模式支持
加入对 shared memory / registers 的 IJON hook 支持
扫描并保存 /proc/self/maps 的内存区域
在翻译完指令后,会将翻译情况传输给父进程forkserver。

接下来是tcg目录下的代码。

  1. tcg/tcg.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
diff --git a/tcg/tcg.c b/tcg/tcg.c
index b1a7465df2..964e91ee32 100644
--- a/tcg/tcg.c
+++ b/tcg/tcg.c
@@ -61,6 +61,8 @@
#include "user/guest-base.h"
#endif

+#include "qemuafl/common.h"
+
/* Forward declarations for functions declared in tcg-target.c.inc and
used here. */
static void tcg_target_init(TCGContext *s);
@@ -2435,6 +2437,17 @@ bool tcg_op_deposit_valid(TCGType type, unsigned ofs, unsigned len)
return TCG_TARGET_deposit_valid(type, ofs, len);
}

+void afl_gen_tcg_plain_call(void *func)
+{
+ TCGOp *op = tcg_emit_op(INDEX_op_call);
+
+ TCGOP_CALLO(op) = 0;
+
+ op->args[0] = (uintptr_t)func;
+ op->args[1] = 0;
+ TCGOP_CALLI(op) = 0;
+}
+
static TCGOp *tcg_op_alloc(TCGOpcode opc, unsigned nargs);

该部分 AFL patch 主要实现一个简单接口,允许在 TCG 翻译过程中插入对指定函数的纯粹函数调用。

  1. tcg/tcg-op.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c
index e2e25ebf7d..e42766c20d 100644
--- a/tcg/tcg-op.c
+++ b/tcg/tcg-op.c
@@ -32,6 +32,25 @@
#include "trace/mem.h"
#include "exec/plugin-gen.h"

+#include "qemuafl/qasan-qemu.h"
+
+#define GEN_QASAN_OP(OP) \
+void qasan_gen_##OP(TCGv addr, int off) { \
+ \
+ if (use_qasan && cur_block_is_good) \
+ gen_helper_qasan_##OP(cpu_env, addr); \
+ \
+}
+
+GEN_QASAN_OP(load1)
+GEN_QASAN_OP(load2)
+GEN_QASAN_OP(load4)
+GEN_QASAN_OP(load8)
+GEN_QASAN_OP(store1)
+GEN_QASAN_OP(store2)
+GEN_QASAN_OP(store4)
+GEN_QASAN_OP(store8)
+

@@ -2836,9 +2857,18 @@ void tcg_gen_qemu_ld_i32(TCGv_i32 val, TCGv addr, TCGArg idx, MemOp memop)
}

addr = plugin_prep_mem_callbacks(addr);
+
+ switch (memop & MO_SIZE) {
+ case MO_64: qasan_gen_load8(addr, idx); break;
+ case MO_32: qasan_gen_load4(addr, idx); break;
+ case MO_16: qasan_gen_load2(addr, idx); break;
+ case MO_8: qasan_gen_load1(addr, idx); break;
+ default: qasan_gen_load4(addr, idx); break;
+ }
+
gen_ldst_i32(INDEX_op_qemu_ld_i32, val, addr, memop, idx);
plugin_gen_mem_callbacks(addr, info);
-
+
if ((orig_memop ^ memop) & MO_BSWAP) {
switch (orig_memop & MO_SIZE) {
case MO_16:
@@ -2883,7 +2913,20 @@ void tcg_gen_qemu_st_i32(TCGv_i32 val, TCGv addr, TCGArg idx, MemOp memop)
}

addr = plugin_prep_mem_callbacks(addr);
- gen_ldst_i32(INDEX_op_qemu_st_i32, val, addr, memop, idx);
+
+ switch (memop & MO_SIZE) {
+ case MO_64: qasan_gen_store8(addr, idx); break;
+ case MO_32: qasan_gen_store4(addr, idx); break;
+ case MO_16: qasan_gen_store2(addr, idx); break;
+ case MO_8: qasan_gen_store1(addr, idx); break;
+ default: qasan_gen_store4(addr, idx); break;
+ }
+
+ if (TCG_TARGET_HAS_qemu_st8_i32 && (memop & MO_SIZE) == MO_8) {
+ gen_ldst_i32(INDEX_op_qemu_st8_i32, val, addr, memop, idx);
+ } else {
+ gen_ldst_i32(INDEX_op_qemu_st_i32, val, addr, memop, idx);
+ }
plugin_gen_mem_callbacks(addr, info);

if (swap) {
@@ -2921,6 +2964,15 @@ void tcg_gen_qemu_ld_i64(TCGv_i64 val, TCGv addr, TCGArg idx, MemOp memop)
}

addr = plugin_prep_mem_callbacks(addr);
+
+ switch (memop & MO_SIZE) {
+ case MO_64: qasan_gen_load8(addr, idx); break;
+ case MO_32: qasan_gen_load4(addr, idx); break;
+ case MO_16: qasan_gen_load2(addr, idx); break;
+ case MO_8: qasan_gen_load1(addr, idx); break;
+ default: qasan_gen_load8(addr, idx); break;
+ }
+
gen_ldst_i64(INDEX_op_qemu_ld_i64, val, addr, memop, idx);
plugin_gen_mem_callbacks(addr, info);

@@ -2984,6 +3036,15 @@ void tcg_gen_qemu_st_i64(TCGv_i64 val, TCGv addr, TCGArg idx, MemOp memop)
}

addr = plugin_prep_mem_callbacks(addr);
+
+ switch (memop & MO_SIZE) {
+ case MO_64: qasan_gen_store8(addr, idx); break;
+ case MO_32: qasan_gen_store4(addr, idx); break;
+ case MO_16: qasan_gen_store2(addr, idx); break;
+ case MO_8: qasan_gen_store1(addr, idx); break;
+ default: qasan_gen_store8(addr, idx); break;
+ }
+
gen_ldst_i64(INDEX_op_qemu_st_i64, val, addr, memop, idx);
plugin_gen_mem_callbacks(addr, info);

这段代码是 QASan实现的核心部分。

简单来说,它的作用是:在 QEMU 将 Guest(客户机)的内存读写指令翻译成中间码(TCG Ops)时,强制插入一段“检查代码”。

最后就是target/{arch}/tcg目录下的代码。

  1. target/mips/tcg/translate.c文件patch 内容如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c
index 78b848a6d9..c4d42da965 100644
--- a/target/mips/tcg/translate.c
+++ b/target/mips/tcg/translate.c
@@ -49,6 +49,27 @@
STUB_HELPER(cache, TCGv_env env, TCGv val, TCGv_i32 reg)
#endif

+/* MIPS_PATCH */
+#include "qemuafl/cpu-translate.h"
+
+/* MIPS_PATCH */
+#define AFL_QEMU_TARGET_MIPS_SNIPPET \
+ if (is_persistent) { \
+ if (ctx->base.pc_next == afl_persistent_addr) { \
+ gen_helper_afl_persistent_routine(cpu_env); \
+ \
+ if (afl_persistent_ret_addr == 0 && !persistent_exits) { \
+ tcg_gen_movi_tl(cpu_gpr[31], afl_persistent_addr); \
+ } \
+ \
+ if (!persistent_save_gpr) afl_gen_tcg_plain_call(&afl_persistent_loop); \
+ \
+ } else if (afl_persistent_ret_addr && \
+ ctx->base.pc_next == afl_persistent_ret_addr) { \
+ gen_goto_tb(ctx, 0, afl_persistent_addr); \
+ } \
+ }
+
enum {
/* indirect opcode tables */
OPC_SPECIAL = (0x00 << 26),
@@ -1187,6 +1208,128 @@ static const char regnames_LO[][4] = {
"LO0", "LO1", "LO2", "LO3",
};

+/* MIPS_PATCH */
+void afl_save_regs(struct api_regs* r, CPUArchState *env) {
+ int i = 0;
+ int j = 0;
+ /* GP registers saving */
+ r->r0 = env->active_tc.gpr[0];
+ r->at = env->active_tc.gpr[1];
+ r->v0 = env->active_tc.gpr[2];
+ r->v1 = env->active_tc.gpr[3];
+ r->a0 = env->active_tc.gpr[4];
+ r->a1 = env->active_tc.gpr[5];
+ r->a2 = env->active_tc.gpr[6];
+ r->a3 = env->active_tc.gpr[7];
+ r->t0 = env->active_tc.gpr[8];
+ r->t1 = env->active_tc.gpr[9];
+ r->t2 = env->active_tc.gpr[10];
+ r->t3 = env->active_tc.gpr[11];
+ r->t4 = env->active_tc.gpr[12];
+ r->t5 = env->active_tc.gpr[13];
+ r->t6 = env->active_tc.gpr[14];
+ r->t7 = env->active_tc.gpr[15];
+ r->s0 = env->active_tc.gpr[16];
+ r->s1 = env->active_tc.gpr[17];
+ r->s2 = env->active_tc.gpr[18];
+ r->s3 = env->active_tc.gpr[19];
+ r->s4 = env->active_tc.gpr[20];
+ r->s5 = env->active_tc.gpr[21];
+ r->s6 = env->active_tc.gpr[22];
+ r->s7 = env->active_tc.gpr[23];
+ r->t8 = env->active_tc.gpr[24];
+ r->t9 = env->active_tc.gpr[25];
+ r->k0 = env->active_tc.gpr[26];
+ r->k1 = env->active_tc.gpr[27];
+ r->gp = env->active_tc.gpr[28];
+ r->sp = env->active_tc.gpr[29];
+ r->fp = env->active_tc.gpr[30];
+ r->ra = env->active_tc.gpr[31];
+ r->PC = env->active_tc.PC;
+#if defined(TARGET_MIPS64)
+ memcpy(r->gpr_hi, env->active_tc.gpr_hi, sizeof(r->gpr_hi));
+#endif
+ for (i = 0; i < MIPS_DSP_ACC; i++) {
+ r->HI[i] = env->active_tc.HI[i];
+ r->LO[i] = env->active_tc.LO[i];
+ }
+ /* FP registers saving */
+ for (i = 0; i < 32; i++) {
+ r->fpr[i].fd = env->active_fpu.fpr[i].fd;
+ for (j = 0; j < 2; j++) {
+ r->fpr[i].fs[j] = env->active_fpu.fpr[i].fs[j];
+ }
+ r->fpr[i].d = env->active_fpu.fpr[i].d;
+ for (j = 0; j < 2; j++) {
+ r->fpr[i].w[j] = env->active_fpu.fpr[i].w[j];
+ }
+ for (j = 0; j < MSA_WRLEN / 8; j++) {
+ r->fpr[i].wr.b[j] = env->active_fpu.fpr[i].wr.b[j];
+ }
+ }
+}
+
+/* MIPS_PATCH */
+void afl_restore_regs(struct api_regs* r, CPUArchState *env) {
+ int i = 0;
+ int j = 0;
+ /* GP registers restoring */
+ env->active_tc.gpr[0] = r->r0;
+ env->active_tc.gpr[1] = r->at;
+ env->active_tc.gpr[2] = r->v0;
+ env->active_tc.gpr[3] = r->v1;
+ env->active_tc.gpr[4] = r->a0;
+ env->active_tc.gpr[5] = r->a1;
+ env->active_tc.gpr[6] = r->a2;
+ env->active_tc.gpr[7] = r->a3;
+ env->active_tc.gpr[8] = r->t0;
+ env->active_tc.gpr[9] = r->t1;
+ env->active_tc.gpr[10] = r->t2;
+ env->active_tc.gpr[11] = r->t3;
+ env->active_tc.gpr[12] = r->t4;
+ env->active_tc.gpr[13] = r->t5;
+ env->active_tc.gpr[14] = r->t6;
+ env->active_tc.gpr[15] = r->t7;
+ env->active_tc.gpr[16] = r->s0;
+ env->active_tc.gpr[17] = r->s1;
+ env->active_tc.gpr[18] = r->s2;
+ env->active_tc.gpr[19] = r->s3;
+ env->active_tc.gpr[20] = r->s4;
+ env->active_tc.gpr[21] = r->s5;
+ env->active_tc.gpr[22] = r->s6;
+ env->active_tc.gpr[23] = r->s7;
+ env->active_tc.gpr[24] = r->t8;
+ env->active_tc.gpr[25] = r->t9;
+ env->active_tc.gpr[26] = r->k0;
+ env->active_tc.gpr[27] = r->k1;
+ env->active_tc.gpr[28] = r->gp;
+ env->active_tc.gpr[29] = r->sp;
+ env->active_tc.gpr[30] = r->fp;
+ env->active_tc.gpr[31] = r->ra;
+ env->active_tc.PC = r->PC;
+#if defined(TARGET_MIPS64)
+ memcpy(env->active_tc.gpr_hi, r->gpr_hi, sizeof(r->gpr_hi));
+#endif
+ for (i = 0; i < MIPS_DSP_ACC; i++) {
+ env->active_tc.HI[i] = r->HI[i];
+ env->active_tc.LO[i] = r->LO[i];
+ }
+ /* FP registers restoring */
+ for (i = 0; i < 32; i++) {
+ env->active_fpu.fpr[i].fd = r->fpr[i].fd;
+ for (j = 0; j < 2; j++) {
+ env->active_fpu.fpr[i].fs[j] = r->fpr[i].fs[j];
+ }
+ env->active_fpu.fpr[i].d = r->fpr[i].d;
+ for (j = 0; j < 2; j++) {
+ env->active_fpu.fpr[i].w[j] = r->fpr[i].w[j];
+ }
+ for (j = 0; j < MSA_WRLEN / 8; j++) {
+ env->active_fpu.fpr[i].wr.b[j] = r->fpr[i].wr.b[j];
+ }
+ }
+}
+
/* General purpose registers moves. */
void gen_load_gpr(TCGv t, int reg)
{
@@ -15138,6 +15281,9 @@ static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs)
int insn_bytes;
int is_slot;

+ /* MIPS_PATCH */
+ AFL_QEMU_TARGET_MIPS_SNIPPET
+
is_slot = ctx->hflags & MIPS_HFLAG_BMASK;
if (ctx->insn_flags & ISA_NANOMIPS32) {
ctx->opcode = translator_lduw(env, &ctx->base, ctx->base.pc_next);

target/{arch}/tcg/translate.c 主要将目标程序的指令翻译成 TCG IR。AFL 对该文件的 patch 主要添加 AFL 持久化模式支持,包括持久化 fuzz 逻辑注入及 MIPS 完整寄存器状态的保存与恢复,使 AFL 能以高效循环方式 fuzz MIPS 程序。

由于测试案例使用 MIPS 架构,因此上述 patch 内容仅包含 MIPS 架构相关部分,其他架构暂未 patch。此外,由于暂未使用 QASAN 功能,许多 QASAN 相关 patch 亦暂未加入。

AFL Fuzz QEMU新版适配:深度解析 Patch 细节

https://nobb.site/2025/11/13/fuzz3/

Author

Hcamael

Posted on

2025-11-13

Updated on

2025-12-16

Licensed under