You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AFL/afl-fuzz.c

8021 lines
240 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
Copyright 2013 Google LLC All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
american fuzzy lop - fuzzer code
--------------------------------
Written and maintained by Michal Zalewski <lcamtuf@google.com>
Forkserver design by Jann Horn <jannhorn@googlemail.com>
This is the real deal: the program takes an instrumented binary and
attempts a variety of basic fuzzing tricks, paying close attention to
how they affect the execution path.
*/
#define AFL_MAIN
#include "android-ashmem.h"
#define MESSAGES_TO_STDOUT
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#define _FILE_OFFSET_BITS 64
#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
#include "hash.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <dirent.h>
#include <ctype.h>
#include <fcntl.h>
#include <termios.h>
#include <dlfcn.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__)
# include <sys/sysctl.h>
#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */
/* For systems that have sched_setaffinity; right now just Linux, but one
can hope... */
#ifdef __linux__
# define HAVE_AFFINITY 1
#endif /* __linux__ */
/* A toggle to export some variables when building as a library. Not very
useful for the general public. */
#ifdef AFL_LIB
# define EXP_ST
#else
# define EXP_ST static
#endif /* ^AFL_LIB */
/* Lots of globals, but mostly for the status UI and other things where it
really makes no sense to haul them around as function parameters. */
EXP_ST u8 *in_dir, /* Input directory with test cases */
*out_file, /* File to fuzz, if any */
*out_dir, /* Working & output directory */
*sync_dir, /* Synchronization directory */
*sync_id, /* Fuzzer ID */
*use_banner, /* Display banner */
*in_bitmap, /* Input bitmap */
*doc_path, /* Path to documentation dir */
*target_path, /* Path to target binary */
*orig_cmdline; /* Original command line */
EXP_ST u32 exec_tmout = EXEC_TIMEOUT; /* Configurable exec timeout (ms) */
static u32 hang_tmout = EXEC_TIMEOUT; /* Timeout used for hang det (ms) */
EXP_ST u64 mem_limit = MEM_LIMIT; /* Memory cap for child (MB) */
EXP_ST u32 cpu_to_bind = 0; /* id of free CPU core to bind */
static u32 stats_update_freq = 1; /* Stats update frequency (execs) */
EXP_ST u8 skip_deterministic, /* Skip deterministic stages? */
force_deterministic, /* Force deterministic stages? */
use_splicing, /* Recombine input files? */
dumb_mode, /* Run in non-instrumented mode? */
score_changed, /* Scoring for favorites changed? */
kill_signal, /* Signal that killed the child */
resuming_fuzz, /* Resuming an older fuzzing job? */
timeout_given, /* Specific timeout given? */
cpu_to_bind_given, /* Specified cpu_to_bind given? */
not_on_tty, /* stdout is not a tty */
term_too_small, /* terminal dimensions too small */
uses_asan, /* Target uses ASAN? */
no_forkserver, /* Disable forkserver? */
crash_mode, /* Crash mode! Yeah! */
in_place_resume, /* Attempt in-place resume? */
auto_changed, /* Auto-generated tokens changed? */
no_cpu_meter_red, /* Feng shui on the status screen */
no_arith, /* Skip most arithmetic ops */
shuffle_queue, /* Shuffle input queue? */
bitmap_changed = 1, /* Time to update bitmap? */
qemu_mode, /* Running in QEMU mode? */
skip_requested, /* Skip request, via SIGUSR1 */
run_over10m, /* Run time over 10 minutes? */
persistent_mode, /* Running in persistent mode? */
deferred_mode, /* Deferred forkserver mode? */
fast_cal; /* Try to calibrate faster? */
static s32 out_fd, /* Persistent fd for out_file */
dev_urandom_fd = -1, /* Persistent fd for /dev/urandom */
dev_null_fd = -1, /* Persistent fd for /dev/null */
fsrv_ctl_fd, /* Fork server control pipe (write) */
fsrv_st_fd; /* Fork server status pipe (read) */
static s32 forksrv_pid, /* PID of the fork server */
child_pid = -1, /* PID of the fuzzed program */
out_dir_fd = -1; /* FD of the lock file */
EXP_ST u8* trace_bits; /* SHM with instrumentation bitmap */
EXP_ST u8 virgin_bits[MAP_SIZE], /* Regions yet untouched by fuzzing */
virgin_tmout[MAP_SIZE], /* Bits we haven't seen in tmouts */
virgin_crash[MAP_SIZE]; /* Bits we haven't seen in crashes */
static u8 var_bytes[MAP_SIZE]; /* Bytes that appear to be variable */
static s32 shm_id; /* ID of the SHM region */
static volatile u8 stop_soon, /* Ctrl-C pressed? */
clear_screen = 1, /* Window resized? */
child_timed_out; /* Traced process timed out? */
EXP_ST u32 queued_paths, /* Total number of queued testcases */
queued_variable, /* Testcases with variable behavior */
queued_at_start, /* Total number of initial inputs */
queued_discovered, /* Items discovered during this run */
queued_imported, /* Items imported via -S */
queued_favored, /* Paths deemed favorable */
queued_with_cov, /* Paths with new coverage bytes */
pending_not_fuzzed, /* Queued but not done yet */
pending_favored, /* Pending favored paths */
cur_skipped_paths, /* Abandoned inputs in cur cycle */
cur_depth, /* Current path depth */
max_depth, /* Max path depth */
useless_at_start, /* Number of useless starting paths */
var_byte_count, /* Bitmap bytes with var behavior */
current_entry, /* Current queue entry ID */
havoc_div = 1; /* Cycle count divisor for havoc */
EXP_ST u64 total_crashes, /* Total number of crashes */
unique_crashes, /* Crashes with unique signatures */
total_tmouts, /* Total number of timeouts */
unique_tmouts, /* Timeouts with unique signatures */
unique_hangs, /* Hangs with unique signatures */
total_execs, /* Total execve() calls */
slowest_exec_ms, /* Slowest testcase non hang in ms */
start_time, /* Unix start time (ms) */
last_path_time, /* Time for most recent path (ms) */
last_crash_time, /* Time for most recent crash (ms) */
last_hang_time, /* Time for most recent hang (ms) */
last_crash_execs, /* Exec counter at last crash */
queue_cycle, /* Queue round counter */
cycles_wo_finds, /* Cycles without any new paths */
trim_execs, /* Execs done to trim input files */
bytes_trim_in, /* Bytes coming into the trimmer */
bytes_trim_out, /* Bytes coming outa the trimmer */
blocks_eff_total, /* Blocks subject to effector maps */
blocks_eff_select; /* Blocks selected as fuzzable */
static u32 subseq_tmouts; /* Number of timeouts in a row */
static u8 *stage_name = "init", /* Name of the current fuzz stage */
*stage_short, /* Short stage name */
*syncing_party; /* Currently syncing with... */
static s32 stage_cur, stage_max; /* Stage progression */
static s32 splicing_with = -1; /* Splicing with which test case? */
static u32 master_id, master_max; /* Master instance job splitting */
static u32 syncing_case; /* Syncing with case #... */
static s32 stage_cur_byte, /* Byte offset of current stage op */
stage_cur_val; /* Value used for stage op */
static u8 stage_val_type; /* Value type (STAGE_VAL_*) */
static u64 stage_finds[32], /* Patterns found per fuzz stage */
stage_cycles[32]; /* Execs per fuzz stage */
static u32 rand_cnt; /* Random number counter */
static u64 total_cal_us, /* Total calibration time (us) */
total_cal_cycles; /* Total calibration cycles */
static u64 total_bitmap_size, /* Total bit count for all bitmaps */
total_bitmap_entries; /* Number of bitmaps counted */
static s32 cpu_core_count; /* CPU core count */
#ifdef HAVE_AFFINITY
static s32 cpu_aff = -1; /* Selected CPU core */
#endif /* HAVE_AFFINITY */
static FILE* plot_file; /* Gnuplot output file */
struct queue_entry {
u8* fname; /* File name for the test case */
u32 len; /* Input length */
u8 cal_failed, /* Calibration failed? */
trim_done, /* Trimmed? */
was_fuzzed, /* Had any fuzzing done yet? */
passed_det, /* Deterministic stages passed? */
has_new_cov, /* Triggers new coverage? */
var_behavior, /* Variable behavior? */
favored, /* Currently favored? */
fs_redundant; /* Marked as redundant in the fs? */
u32 bitmap_size, /* Number of bits set in bitmap */
exec_cksum; /* Checksum of the execution trace */
u64 exec_us, /* Execution time (us) */
handicap, /* Number of queue cycles behind */
depth; /* Path depth */
u8* trace_mini; /* Trace bytes, if kept */
u32 tc_ref; /* Trace bytes ref count */
struct queue_entry *next, /* Next element, if any */
*next_100; /* 100 elements ahead */
};
static struct queue_entry *queue, /* Fuzzing queue (linked list) */
*queue_cur, /* Current offset within the queue */
*queue_top, /* Top of the list */
*q_prev100; /* Previous 100 marker */
static struct queue_entry*
top_rated[MAP_SIZE]; /* Top entries for bitmap bytes */
struct extra_data {
u8* data; /* Dictionary token data */
u32 len; /* Dictionary token length */
u32 hit_cnt; /* Use count in the corpus */
};
static struct extra_data* extras; /* Extra tokens to fuzz with */
static u32 extras_cnt; /* Total number of tokens read */
static struct extra_data* a_extras; /* Automatically selected extras */
static u32 a_extras_cnt; /* Total number of tokens available */
static u8* (*post_handler)(u8* buf, u32* len);
/* Interesting values, as per config.h */
static s8 interesting_8[] = { INTERESTING_8 };
static s16 interesting_16[] = { INTERESTING_8, INTERESTING_16 };
static s32 interesting_32[] = { INTERESTING_8, INTERESTING_16, INTERESTING_32 };
/* Fuzzing stages */
enum {
/* 00 */ STAGE_FLIP1,
/* 01 */ STAGE_FLIP2,
/* 02 */ STAGE_FLIP4,
/* 03 */ STAGE_FLIP8,
/* 04 */ STAGE_FLIP16,
/* 05 */ STAGE_FLIP32,
/* 06 */ STAGE_ARITH8,
/* 07 */ STAGE_ARITH16,
/* 08 */ STAGE_ARITH32,
/* 09 */ STAGE_INTEREST8,
/* 10 */ STAGE_INTEREST16,
/* 11 */ STAGE_INTEREST32,
/* 12 */ STAGE_EXTRAS_UO,
/* 13 */ STAGE_EXTRAS_UI,
/* 14 */ STAGE_EXTRAS_AO,
/* 15 */ STAGE_HAVOC,
/* 16 */ STAGE_SPLICE
};
/* Stage value types */
enum {
/* 00 */ STAGE_VAL_NONE,
/* 01 */ STAGE_VAL_LE,
/* 02 */ STAGE_VAL_BE
};
/* Execution status fault codes */
enum {
/* 00 */ FAULT_NONE,
/* 01 */ FAULT_TMOUT,
/* 02 */ FAULT_CRASH,
/* 03 */ FAULT_ERROR,
/* 04 */ FAULT_NOINST,
/* 05 */ FAULT_NOBITS
};
/* Get unix time in milliseconds */
static u64 get_cur_time(void) {
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000);
}
/* Get unix time in microseconds */
static u64 get_cur_time_us(void) {
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (tv.tv_sec * 1000000ULL) + tv.tv_usec;
}
/* Generate a random number (from 0 to limit - 1). This may
have slight bias. */
static inline u32 UR(u32 limit) {
if (unlikely(!rand_cnt--)) {
u32 seed[2];
ck_read(dev_urandom_fd, &seed, sizeof(seed), "/dev/urandom");
srandom(seed[0]);
rand_cnt = (RESEED_RNG / 2) + (seed[1] % RESEED_RNG);
}
return random() % limit;
}
/* Shuffle an array of pointers. Might be slightly biased. */
static void shuffle_ptrs(void** ptrs, u32 cnt) {
u32 i;
for (i = 0; i < cnt - 2; i++) {
u32 j = i + UR(cnt - i);
void *s = ptrs[i];
ptrs[i] = ptrs[j];
ptrs[j] = s;
}
}
#ifdef HAVE_AFFINITY
/* Build a list of processes bound to specific cores. Returns -1 if nothing
can be found. Assumes an upper bound of 4k CPUs. */
static void bind_to_free_cpu(void) {
DIR* d;
struct dirent* de;
cpu_set_t c;
u8 cpu_used[4096] = { 0 };
u32 i;
if (cpu_core_count < 2) return;
if (getenv("AFL_NO_AFFINITY")) {
WARNF("Not binding to a CPU core (AFL_NO_AFFINITY set).");
return;
}
d = opendir("/proc");
if (!d) {
WARNF("Unable to access /proc - can't scan for free CPU cores.");
return;
}
ACTF("Checking CPU core loadout...");
/* Introduce some jitter, in case multiple AFL tasks are doing the same
thing at the same time... */
usleep(R(1000) * 250);
/* Scan all /proc/<pid>/status entries, checking for Cpus_allowed_list.
Flag all processes bound to a specific CPU using cpu_used[]. This will
fail for some exotic binding setups, but is likely good enough in almost
all real-world use cases. */
while ((de = readdir(d))) {
u8* fn;
FILE* f;
u8 tmp[MAX_LINE];
u8 has_vmsize = 0;
if (!isdigit(de->d_name[0])) continue;
fn = alloc_printf("/proc/%s/status", de->d_name);
if (!(f = fopen(fn, "r"))) {
ck_free(fn);
continue;
}
while (fgets(tmp, MAX_LINE, f)) {
u32 hval;
/* Processes without VmSize are probably kernel tasks. */
if (!strncmp(tmp, "VmSize:\t", 8)) has_vmsize = 1;
if (!strncmp(tmp, "Cpus_allowed_list:\t", 19) &&
!strchr(tmp, '-') && !strchr(tmp, ',') &&
sscanf(tmp + 19, "%u", &hval) == 1 && hval < sizeof(cpu_used) &&
has_vmsize) {
cpu_used[hval] = 1;
break;
}
}
ck_free(fn);
fclose(f);
}
closedir(d);
if (cpu_to_bind_given) {
if (cpu_to_bind >= cpu_core_count)
FATAL("The CPU core id to bind should be between 0 and %u", cpu_core_count - 1);
if (cpu_used[cpu_to_bind])
FATAL("The CPU core #%u to bind is not free!", cpu_to_bind);
i = cpu_to_bind;
} else {
for (i = 0; i < cpu_core_count; i++) if (!cpu_used[i]) break;
}
if (i == cpu_core_count) {
SAYF("\n" cLRD "[-] " cRST
"Uh-oh, looks like all %u CPU cores on your system are allocated to\n"
" other instances of afl-fuzz (or similar CPU-locked tasks). Starting\n"
" another fuzzer on this machine is probably a bad plan, but if you are\n"
" absolutely sure, you can set AFL_NO_AFFINITY and try again.\n",
cpu_core_count);
FATAL("No more free CPU cores");
}
OKF("Found a free CPU core, binding to #%u.", i);
cpu_aff = i;
CPU_ZERO(&c);
CPU_SET(i, &c);
if (sched_setaffinity(0, sizeof(c), &c))
PFATAL("sched_setaffinity failed");
}
#endif /* HAVE_AFFINITY */
#ifndef IGNORE_FINDS
/* Helper function to compare buffers; returns first and last differing offset. We
use this to find reasonable locations for splicing two files. */
static void locate_diffs(u8* ptr1, u8* ptr2, u32 len, s32* first, s32* last) {
s32 f_loc = -1;
s32 l_loc = -1;
u32 pos;
for (pos = 0; pos < len; pos++) {
if (*(ptr1++) != *(ptr2++)) {
if (f_loc == -1) f_loc = pos;
l_loc = pos;
}
}
*first = f_loc;
*last = l_loc;
return;
}
#endif /* !IGNORE_FINDS */
/* Describe integer. Uses 12 cyclic static buffers for return values. The value
returned should be five characters or less for all the integers we reasonably
expect to see. */
static u8* DI(u64 val) {
static u8 tmp[12][16];
static u8 cur;
cur = (cur + 1) % 12;
#define CHK_FORMAT(_divisor, _limit_mult, _fmt, _cast) do { \
if (val < (_divisor) * (_limit_mult)) { \
sprintf(tmp[cur], _fmt, ((_cast)val) / (_divisor)); \
return tmp[cur]; \
} \
} while (0)
/* 0-9999 */
CHK_FORMAT(1, 10000, "%llu", u64);
/* 10.0k - 99.9k */
CHK_FORMAT(1000, 99.95, "%0.01fk", double);
/* 100k - 999k */
CHK_FORMAT(1000, 1000, "%lluk", u64);
/* 1.00M - 9.99M */
CHK_FORMAT(1000 * 1000, 9.995, "%0.02fM", double);
/* 10.0M - 99.9M */
CHK_FORMAT(1000 * 1000, 99.95, "%0.01fM", double);
/* 100M - 999M */
CHK_FORMAT(1000 * 1000, 1000, "%lluM", u64);
/* 1.00G - 9.99G */
CHK_FORMAT(1000LL * 1000 * 1000, 9.995, "%0.02fG", double);
/* 10.0G - 99.9G */
CHK_FORMAT(1000LL * 1000 * 1000, 99.95, "%0.01fG", double);
/* 100G - 999G */
CHK_FORMAT(1000LL * 1000 * 1000, 1000, "%lluG", u64);
/* 1.00T - 9.99G */
CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 9.995, "%0.02fT", double);
/* 10.0T - 99.9T */
CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 99.95, "%0.01fT", double);
/* 100T+ */
strcpy(tmp[cur], "infty");
return tmp[cur];
}
/* Describe float. Similar to the above, except with a single
static buffer. */
static u8* DF(double val) {
static u8 tmp[16];
if (val < 99.995) {
sprintf(tmp, "%0.02f", val);
return tmp;
}
if (val < 999.95) {
sprintf(tmp, "%0.01f", val);
return tmp;
}
return DI((u64)val);
}
/* Describe integer as memory size. */
static u8* DMS(u64 val) {
static u8 tmp[12][16];
static u8 cur;
cur = (cur + 1) % 12;
/* 0-9999 */
CHK_FORMAT(1, 10000, "%llu B", u64);
/* 10.0k - 99.9k */
CHK_FORMAT(1024, 99.95, "%0.01f kB", double);
/* 100k - 999k */
CHK_FORMAT(1024, 1000, "%llu kB", u64);
/* 1.00M - 9.99M */
CHK_FORMAT(1024 * 1024, 9.995, "%0.02f MB", double);
/* 10.0M - 99.9M */
CHK_FORMAT(1024 * 1024, 99.95, "%0.01f MB", double);
/* 100M - 999M */
CHK_FORMAT(1024 * 1024, 1000, "%llu MB", u64);
/* 1.00G - 9.99G */
CHK_FORMAT(1024LL * 1024 * 1024, 9.995, "%0.02f GB", double);
/* 10.0G - 99.9G */
CHK_FORMAT(1024LL * 1024 * 1024, 99.95, "%0.01f GB", double);
/* 100G - 999G */
CHK_FORMAT(1024LL * 1024 * 1024, 1000, "%llu GB", u64);
/* 1.00T - 9.99G */
CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 9.995, "%0.02f TB", double);
/* 10.0T - 99.9T */
CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 99.95, "%0.01f TB", double);
#undef CHK_FORMAT
/* 100T+ */
strcpy(tmp[cur], "infty");
return tmp[cur];
}
/* Describe time delta. Returns one static buffer, 34 chars of less. */
static u8* DTD(u64 cur_ms, u64 event_ms) {
static u8 tmp[64];
u64 delta;
s32 t_d, t_h, t_m, t_s;
if (!event_ms) return "none seen yet";
delta = cur_ms - event_ms;
t_d = delta / 1000 / 60 / 60 / 24;
t_h = (delta / 1000 / 60 / 60) % 24;
t_m = (delta / 1000 / 60) % 60;
t_s = (delta / 1000) % 60;
sprintf(tmp, "%s days, %u hrs, %u min, %u sec", DI(t_d), t_h, t_m, t_s);
return tmp;
}
/* Mark deterministic checks as done for a particular queue entry. We use the
.state file to avoid repeating deterministic fuzzing when resuming aborted
scans. */
static void mark_as_det_done(struct queue_entry* q) {
u8* fn = strrchr(q->fname, '/');
s32 fd;
fn = alloc_printf("%s/queue/.state/deterministic_done/%s", out_dir, fn + 1);
fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
close(fd);
ck_free(fn);
q->passed_det = 1;
}
/* Mark as variable. Create symlinks if possible to make it easier to examine
the files. */
static void mark_as_variable(struct queue_entry* q) {
u8 *fn = strrchr(q->fname, '/') + 1, *ldest;
ldest = alloc_printf("../../%s", fn);
fn = alloc_printf("%s/queue/.state/variable_behavior/%s", out_dir, fn);
if (symlink(ldest, fn)) {
s32 fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
close(fd);
}
ck_free(ldest);
ck_free(fn);
q->var_behavior = 1;
}
/* Mark / unmark as redundant (edge-only). This is not used for restoring state,
but may be useful for post-processing datasets. */
static void mark_as_redundant(struct queue_entry* q, u8 state) {
u8* fn;
s32 fd;
if (state == q->fs_redundant) return;
q->fs_redundant = state;
fn = strrchr(q->fname, '/');
fn = alloc_printf("%s/queue/.state/redundant_edges/%s", out_dir, fn + 1);
if (state) {
fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
close(fd);
} else {
if (unlink(fn)) PFATAL("Unable to remove '%s'", fn);
}
ck_free(fn);
}
/* Append new test case to the queue. */
static void add_to_queue(u8* fname, u32 len, u8 passed_det) {
struct queue_entry* q = ck_alloc(sizeof(struct queue_entry));
q->fname = fname;
q->len = len;
q->depth = cur_depth + 1;
q->passed_det = passed_det;
if (q->depth > max_depth) max_depth = q->depth;
if (queue_top) {
queue_top->next = q;
queue_top = q;
} else q_prev100 = queue = queue_top = q;
queued_paths++;
pending_not_fuzzed++;
cycles_wo_finds = 0;
/* Set next_100 pointer for every 100th element (index 0, 100, etc) to allow faster iteration. */
if ((queued_paths - 1) % 100 == 0 && queued_paths > 1) {
q_prev100->next_100 = q;
q_prev100 = q;
}
last_path_time = get_cur_time();
}
/* Destroy the entire queue. */
EXP_ST void destroy_queue(void) {
struct queue_entry *q = queue, *n;
while (q) {
n = q->next;
ck_free(q->fname);
ck_free(q->trace_mini);
ck_free(q);
q = n;
}
}
/* Write bitmap to file. The bitmap is useful mostly for the secret
-B option, to focus a separate fuzzing session on a particular
interesting input without rediscovering all the others. */
EXP_ST void write_bitmap(void) {
u8* fname;
s32 fd;
if (!bitmap_changed) return;
bitmap_changed = 0;
fname = alloc_printf("%s/fuzz_bitmap", out_dir);
fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) PFATAL("Unable to open '%s'", fname);
ck_write(fd, virgin_bits, MAP_SIZE, fname);
close(fd);
ck_free(fname);
}
/* Read bitmap from file. This is for the -B option again. */
EXP_ST void read_bitmap(u8* fname) {
s32 fd = open(fname, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", fname);
ck_read(fd, virgin_bits, MAP_SIZE, fname);
close(fd);
}
/* Check if the current execution path brings anything new to the table.
Update virgin bits to reflect the finds. Returns 1 if the only change is
the hit-count for a particular tuple; 2 if there are new tuples seen.
Updates the map, so subsequent calls will always return 0.
This function is called after every exec() on a fairly large buffer, so
it needs to be fast. We do this in 32-bit and 64-bit flavors. */
static inline u8 has_new_bits(u8* virgin_map) {
#ifdef WORD_SIZE_64
u64* current = (u64*)trace_bits;
u64* virgin = (u64*)virgin_map;
u32 i = (MAP_SIZE >> 3);
#else
u32* current = (u32*)trace_bits;
u32* virgin = (u32*)virgin_map;
u32 i = (MAP_SIZE >> 2);
#endif /* ^WORD_SIZE_64 */
u8 ret = 0;
while (i--) {
/* Optimize for (*current & *virgin) == 0 - i.e., no bits in current bitmap
that have not been already cleared from the virgin map - since this will
almost always be the case. */
if (unlikely(*current) && unlikely(*current & *virgin)) {
if (likely(ret < 2)) {
u8* cur = (u8*)current;
u8* vir = (u8*)virgin;
/* Looks like we have not found any new bytes yet; see if any non-zero
bytes in current[] are pristine in virgin[]. */
#ifdef WORD_SIZE_64
if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
(cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff) ||
(cur[4] && vir[4] == 0xff) || (cur[5] && vir[5] == 0xff) ||
(cur[6] && vir[6] == 0xff) || (cur[7] && vir[7] == 0xff)) ret = 2;
else ret = 1;
#else
if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
(cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff)) ret = 2;
else ret = 1;
#endif /* ^WORD_SIZE_64 */
}
*virgin &= ~*current;
}
current++;
virgin++;
}
if (ret && virgin_map == virgin_bits) bitmap_changed = 1;
return ret;
}
/* Count the number of bits set in the provided bitmap. Used for the status
screen several times every second, does not have to be fast. */
static u32 count_bits(u8* mem) {
u32* ptr = (u32*)mem;
u32 i = (MAP_SIZE >> 2);
u32 ret = 0;
while (i--) {
u32 v = *(ptr++);
/* This gets called on the inverse, virgin bitmap; optimize for sparse
data. */
if (v == 0xffffffff) {
ret += 32;
continue;
}
v -= ((v >> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
ret += (((v + (v >> 4)) & 0xF0F0F0F) * 0x01010101) >> 24;
}
return ret;
}
#define FF(_b) (0xff << ((_b) << 3))
/* Count the number of bytes set in the bitmap. Called fairly sporadically,
mostly to update the status screen or calibrate and examine confirmed
new paths. */
static u32 count_bytes(u8* mem) {
u32* ptr = (u32*)mem;
u32 i = (MAP_SIZE >> 2);
u32 ret = 0;
while (i--) {
u32 v = *(ptr++);
if (!v) continue;
if (v & FF(0)) ret++;
if (v & FF(1)) ret++;
if (v & FF(2)) ret++;
if (v & FF(3)) ret++;
}
return ret;
}
/* Count the number of non-255 bytes set in the bitmap. Used strictly for the
status screen, several calls per second or so. */
static u32 count_non_255_bytes(u8* mem) {
u32* ptr = (u32*)mem;
u32 i = (MAP_SIZE >> 2);
u32 ret = 0;
while (i--) {
u32 v = *(ptr++);
/* This is called on the virgin bitmap, so optimize for the most likely
case. */
if (v == 0xffffffff) continue;
if ((v & FF(0)) != FF(0)) ret++;
if ((v & FF(1)) != FF(1)) ret++;
if ((v & FF(2)) != FF(2)) ret++;
if ((v & FF(3)) != FF(3)) ret++;
}
return ret;
}
/* Destructively simplify trace by eliminating hit count information
and replacing it with 0x80 or 0x01 depending on whether the tuple
is hit or not. Called on every new crash or timeout, should be
reasonably fast. */
static const u8 simplify_lookup[256] = {
[0] = 1,
[1 ... 255] = 128
};
#ifdef WORD_SIZE_64
static void simplify_trace(u64* mem) {
u32 i = MAP_SIZE >> 3;
while (i--) {
/* Optimize for sparse bitmaps. */
if (unlikely(*mem)) {
u8* mem8 = (u8*)mem;
mem8[0] = simplify_lookup[mem8[0]];
mem8[1] = simplify_lookup[mem8[1]];
mem8[2] = simplify_lookup[mem8[2]];
mem8[3] = simplify_lookup[mem8[3]];
mem8[4] = simplify_lookup[mem8[4]];
mem8[5] = simplify_lookup[mem8[5]];
mem8[6] = simplify_lookup[mem8[6]];
mem8[7] = simplify_lookup[mem8[7]];
} else *mem = 0x0101010101010101ULL;
mem++;
}
}
#else
static void simplify_trace(u32* mem) {
u32 i = MAP_SIZE >> 2;
while (i--) {
/* Optimize for sparse bitmaps. */
if (unlikely(*mem)) {
u8* mem8 = (u8*)mem;
mem8[0] = simplify_lookup[mem8[0]];
mem8[1] = simplify_lookup[mem8[1]];
mem8[2] = simplify_lookup[mem8[2]];
mem8[3] = simplify_lookup[mem8[3]];
} else *mem = 0x01010101;
mem++;
}
}
#endif /* ^WORD_SIZE_64 */
/* Destructively classify execution counts in a trace. This is used as a
preprocessing step for any newly acquired traces. Called on every exec,
must be fast. */
static const u8 count_class_lookup8[256] = {
[0] = 0,
[1] = 1,
[2] = 2,
[3] = 4,
[4 ... 7] = 8,
[8 ... 15] = 16,
[16 ... 31] = 32,
[32 ... 127] = 64,
[128 ... 255] = 128
};
static u16 count_class_lookup16[65536];
EXP_ST void init_count_class16(void) {
u32 b1, b2;
for (b1 = 0; b1 < 256; b1++)
for (b2 = 0; b2 < 256; b2++)
count_class_lookup16[(b1 << 8) + b2] =
(count_class_lookup8[b1] << 8) |
count_class_lookup8[b2];
}
#ifdef WORD_SIZE_64
static inline void classify_counts(u64* mem) {
u32 i = MAP_SIZE >> 3;
while (i--) {
/* Optimize for sparse bitmaps. */
if (unlikely(*mem)) {
u16* mem16 = (u16*)mem;
mem16[0] = count_class_lookup16[mem16[0]];
mem16[1] = count_class_lookup16[mem16[1]];
mem16[2] = count_class_lookup16[mem16[2]];
mem16[3] = count_class_lookup16[mem16[3]];
}
mem++;
}
}
#else
static inline void classify_counts(u32* mem) {
u32 i = MAP_SIZE >> 2;
while (i--) {
/* Optimize for sparse bitmaps. */
if (unlikely(*mem)) {
u16* mem16 = (u16*)mem;
mem16[0] = count_class_lookup16[mem16[0]];
mem16[1] = count_class_lookup16[mem16[1]];
}
mem++;
}
}
#endif /* ^WORD_SIZE_64 */
/* Get rid of shared memory (atexit handler). */
static void remove_shm(void) {
shmctl(shm_id, IPC_RMID, NULL);
}
/* Compact trace bytes into a smaller bitmap. We effectively just drop the
count information here. This is called only sporadically, for some
new paths. */
static void minimize_bits(u8* dst, u8* src) {
u32 i = 0;
while (i < MAP_SIZE) {
if (*(src++)) dst[i >> 3] |= 1 << (i & 7);
i++;
}
}
/* When we bump into a new path, we call this to see if the path appears
more "favorable" than any of the existing ones. The purpose of the
"favorables" is to have a minimal set of paths that trigger all the bits
seen in the bitmap so far, and focus on fuzzing them at the expense of
the rest.
The first step of the process is to maintain a list of top_rated[] entries
for every byte in the bitmap. We win that slot if there is no previous
contender, or if the contender has a more favorable speed x size factor. */
static void update_bitmap_score(struct queue_entry* q) {
u32 i;
u64 fav_factor = q->exec_us * q->len;
/* For every byte set in trace_bits[], see if there is a previous winner,
and how it compares to us. */
for (i = 0; i < MAP_SIZE; i++)
if (trace_bits[i]) {
if (top_rated[i]) {
/* Faster-executing or smaller test cases are favored. */
if (fav_factor > top_rated[i]->exec_us * top_rated[i]->len) continue;
/* Looks like we're going to win. Decrease ref count for the
previous winner, discard its trace_bits[] if necessary. */
if (!--top_rated[i]->tc_ref) {
ck_free(top_rated[i]->trace_mini);
top_rated[i]->trace_mini = 0;
}
}
/* Insert ourselves as the new winner. */
top_rated[i] = q;
q->tc_ref++;
if (!q->trace_mini) {
q->trace_mini = ck_alloc(MAP_SIZE >> 3);
minimize_bits(q->trace_mini, trace_bits);
}
score_changed = 1;
}
}
/* The second part of the mechanism discussed above is a routine that
goes over top_rated[] entries, and then sequentially grabs winners for
previously-unseen bytes (temp_v) and marks them as favored, at least
until the next run. The favored entries are given more air time during
all fuzzing steps. */
static void cull_queue(void) {
struct queue_entry* q;
static u8 temp_v[MAP_SIZE >> 3];
u32 i;
if (dumb_mode || !score_changed) return;
score_changed = 0;
memset(temp_v, 255, MAP_SIZE >> 3);
queued_favored = 0;
pending_favored = 0;
q = queue;
while (q) {
q->favored = 0;
q = q->next;
}
/* Let's see if anything in the bitmap isn't captured in temp_v.
If yes, and if it has a top_rated[] contender, let's use it. */
for (i = 0; i < MAP_SIZE; i++)
if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) {
u32 j = MAP_SIZE >> 3;
/* Remove all bits belonging to the current entry from temp_v. */
while (j--)
if (top_rated[i]->trace_mini[j])
temp_v[j] &= ~top_rated[i]->trace_mini[j];
top_rated[i]->favored = 1;
queued_favored++;
if (!top_rated[i]->was_fuzzed) pending_favored++;
}
q = queue;
while (q) {
mark_as_redundant(q, !q->favored);
q = q->next;
}
}
/* Configure shared memory and virgin_bits. This is called at startup. */
EXP_ST void setup_shm(void) {
u8* shm_str;
if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE);
memset(virgin_tmout, 255, MAP_SIZE);
memset(virgin_crash, 255, MAP_SIZE);
shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);
if (shm_id < 0) PFATAL("shmget() failed");
atexit(remove_shm);
shm_str = alloc_printf("%d", shm_id);
/* If somebody is asking us to fuzz instrumented binaries in dumb mode,
we don't want them to detect instrumentation, since we won't be sending
fork server commands. This should be replaced with better auto-detection
later on, perhaps? */
if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1);
ck_free(shm_str);
trace_bits = shmat(shm_id, NULL, 0);
if (trace_bits == (void *)-1) PFATAL("shmat() failed");
}
/* Load postprocessor, if available. */
static void setup_post(void) {
void* dh;
u8* fn = getenv("AFL_POST_LIBRARY");
u32 tlen = 6;
if (!fn) return;
ACTF("Loading postprocessor from '%s'...", fn);
dh = dlopen(fn, RTLD_NOW);
if (!dh) FATAL("%s", dlerror());
post_handler = dlsym(dh, "afl_postprocess");
if (!post_handler) FATAL("Symbol 'afl_postprocess' not found.");
/* Do a quick test. It's better to segfault now than later =) */
post_handler("hello", &tlen);
OKF("Postprocessor installed successfully.");
}
/* Read all testcases from the input directory, then queue them for testing.
Called at startup. */
static void read_testcases(void) {
struct dirent **nl;
s32 nl_cnt;
u32 i;
u8* fn;
/* Auto-detect non-in-place resumption attempts. */
fn = alloc_printf("%s/queue", in_dir);
if (!access(fn, F_OK)) in_dir = fn; else ck_free(fn);
ACTF("Scanning '%s'...", in_dir);
/* We use scandir() + alphasort() rather than readdir() because otherwise,
the ordering of test cases would vary somewhat randomly and would be
difficult to control. */
nl_cnt = scandir(in_dir, &nl, NULL, alphasort);
if (nl_cnt < 0) {
if (errno == ENOENT || errno == ENOTDIR)
SAYF("\n" cLRD "[-] " cRST
"The input directory does not seem to be valid - try again. The fuzzer needs\n"
" one or more test case to start with - ideally, a small file under 1 kB\n"
" or so. The cases must be stored as regular files directly in the input\n"
" directory.\n");
PFATAL("Unable to open '%s'", in_dir);
}
if (shuffle_queue && nl_cnt > 1) {
ACTF("Shuffling queue...");
shuffle_ptrs((void**)nl, nl_cnt);
}
for (i = 0; i < nl_cnt; i++) {
struct stat st;
u8* fn = alloc_printf("%s/%s", in_dir, nl[i]->d_name);
u8* dfn = alloc_printf("%s/.state/deterministic_done/%s", in_dir, nl[i]->d_name);
u8 passed_det = 0;
free(nl[i]); /* not tracked */
if (lstat(fn, &st) || access(fn, R_OK))
PFATAL("Unable to access '%s'", fn);
/* This also takes care of . and .. */
if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn, "/README.testcases")) {
ck_free(fn);
ck_free(dfn);
continue;
}
if (st.st_size > MAX_FILE)
FATAL("Test case '%s' is too big (%s, limit is %s)", fn,
DMS(st.st_size), DMS(MAX_FILE));
/* Check for metadata that indicates that deterministic fuzzing
is complete for this entry. We don't want to repeat deterministic
fuzzing when resuming aborted scans, because it would be pointless
and probably very time-consuming. */
if (!access(dfn, F_OK)) passed_det = 1;
ck_free(dfn);
add_to_queue(fn, st.st_size, passed_det);
}
free(nl); /* not tracked */
if (!queued_paths) {
SAYF("\n" cLRD "[-] " cRST
"Looks like there are no valid test cases in the input directory! The fuzzer\n"
" needs one or more test case to start with - ideally, a small file under\n"
" 1 kB or so. The cases must be stored as regular files directly in the\n"
" input directory.\n");
FATAL("No usable test cases in '%s'", in_dir);
}
last_path_time = 0;
queued_at_start = queued_paths;
}
/* Helper function for load_extras. */
static int compare_extras_len(const void* p1, const void* p2) {
struct extra_data *e1 = (struct extra_data*)p1,
*e2 = (struct extra_data*)p2;
return e1->len - e2->len;
}
static int compare_extras_use_d(const void* p1, const void* p2) {
struct extra_data *e1 = (struct extra_data*)p1,
*e2 = (struct extra_data*)p2;
return e2->hit_cnt - e1->hit_cnt;
}
/* Read extras from a file, sort by size. */
static void load_extras_file(u8* fname, u32* min_len, u32* max_len,
u32 dict_level) {
FILE* f;
u8 buf[MAX_LINE];
u8 *lptr;
u32 cur_line = 0;
f = fopen(fname, "r");
if (!f) PFATAL("Unable to open '%s'", fname);
while ((lptr = fgets(buf, MAX_LINE, f))) {
u8 *rptr, *wptr;
u32 klen = 0;
cur_line++;
/* Trim on left and right. */
while (isspace(*lptr)) lptr++;
rptr = lptr + strlen(lptr) - 1;
while (rptr >= lptr && isspace(*rptr)) rptr--;
rptr++;
*rptr = 0;
/* Skip empty lines and comments. */
if (!*lptr || *lptr == '#') continue;
/* All other lines must end with '"', which we can consume. */
rptr--;
if (rptr < lptr || *rptr != '"')
FATAL("Malformed name=\"value\" pair in line %u.", cur_line);
*rptr = 0;
/* Skip alphanumerics and dashes (label). */
while (isalnum(*lptr) || *lptr == '_') lptr++;
/* If @number follows, parse that. */
if (*lptr == '@') {
lptr++;
if (atoi(lptr) > dict_level) continue;
while (isdigit(*lptr)) lptr++;
}
/* Skip whitespace and = signs. */
while (isspace(*lptr) || *lptr == '=') lptr++;
/* Consume opening '"'. */
if (*lptr != '"')
FATAL("Malformed name=\"keyword\" pair in line %u.", cur_line);
lptr++;
if (!*lptr) FATAL("Empty keyword in line %u.", cur_line);
/* Okay, let's allocate memory and copy data between "...", handling
\xNN escaping, \\, and \". */
extras = ck_realloc_block(extras, (extras_cnt + 1) *
sizeof(struct extra_data));
wptr = extras[extras_cnt].data = ck_alloc(rptr - lptr);
while (*lptr) {
char* hexdigits = "0123456789abcdef";
switch (*lptr) {
case 1 ... 31:
case 128 ... 255:
FATAL("Non-printable characters in line %u.", cur_line);
case '\\':
lptr++;
if (*lptr == '\\' || *lptr == '"') {
*(wptr++) = *(lptr++);
klen++;
break;
}
if (*lptr != 'x' || !isxdigit(lptr[1]) || !isxdigit(lptr[2]))
FATAL("Invalid escaping (not \\xNN) in line %u.", cur_line);
*(wptr++) =
((strchr(hexdigits, tolower(lptr[1])) - hexdigits) << 4) |
(strchr(hexdigits, tolower(lptr[2])) - hexdigits);
lptr += 3;
klen++;
break;
default:
*(wptr++) = *(lptr++);
klen++;
}
}
extras[extras_cnt].len = klen;
if (extras[extras_cnt].len > MAX_DICT_FILE)
FATAL("Keyword too big in line %u (%s, limit is %s)", cur_line,
DMS(klen), DMS(MAX_DICT_FILE));
if (*min_len > klen) *min_len = klen;
if (*max_len < klen) *max_len = klen;
extras_cnt++;
}
fclose(f);
}
/* Read extras from the extras directory and sort them by size. */
static void load_extras(u8* dir) {
DIR* d;
struct dirent* de;
u32 min_len = MAX_DICT_FILE, max_len = 0, dict_level = 0;
u8* x;
/* If the name ends with @, extract level and continue. */
if ((x = strchr(dir, '@'))) {
*x = 0;
dict_level = atoi(x + 1);
}
ACTF("Loading extra dictionary from '%s' (level %u)...", dir, dict_level);
d = opendir(dir);
if (!d) {
if (errno == ENOTDIR) {
load_extras_file(dir, &min_len, &max_len, dict_level);
goto check_and_sort;
}
PFATAL("Unable to open '%s'", dir);
}
if (x) FATAL("Dictionary levels not supported for directories.");
while ((de = readdir(d))) {
struct stat st;
u8* fn = alloc_printf("%s/%s", dir, de->d_name);
s32 fd;
if (lstat(fn, &st) || access(fn, R_OK))
PFATAL("Unable to access '%s'", fn);
/* This also takes care of . and .. */
if (!S_ISREG(st.st_mode) || !st.st_size) {
ck_free(fn);
continue;
}
if (st.st_size > MAX_DICT_FILE)
FATAL("Extra '%s' is too big (%s, limit is %s)", fn,
DMS(st.st_size), DMS(MAX_DICT_FILE));
if (min_len > st.st_size) min_len = st.st_size;
if (max_len < st.st_size) max_len = st.st_size;
extras = ck_realloc_block(extras, (extras_cnt + 1) *
sizeof(struct extra_data));
extras[extras_cnt].data = ck_alloc(st.st_size);
extras[extras_cnt].len = st.st_size;
fd = open(fn, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", fn);
ck_read(fd, extras[extras_cnt].data, st.st_size, fn);
close(fd);
ck_free(fn);
extras_cnt++;
}
closedir(d);
check_and_sort:
if (!extras_cnt) FATAL("No usable files in '%s'", dir);
qsort(extras, extras_cnt, sizeof(struct extra_data), compare_extras_len);
OKF("Loaded %u extra tokens, size range %s to %s.", extras_cnt,
DMS(min_len), DMS(max_len));
if (max_len > 32)
WARNF("Some tokens are relatively large (%s) - consider trimming.",
DMS(max_len));
if (extras_cnt > MAX_DET_EXTRAS)
WARNF("More than %u tokens - will use them probabilistically.",
MAX_DET_EXTRAS);
}
/* Helper function for maybe_add_auto() */
static inline u8 memcmp_nocase(u8* m1, u8* m2, u32 len) {
while (len--) if (tolower(*(m1++)) ^ tolower(*(m2++))) return 1;
return 0;
}
/* Maybe add automatic extra. */
static void maybe_add_auto(u8* mem, u32 len) {
u32 i;
/* Allow users to specify that they don't want auto dictionaries. */
if (!MAX_AUTO_EXTRAS || !USE_AUTO_EXTRAS) return;
/* Skip runs of identical bytes. */
for (i = 1; i < len; i++)
if (mem[0] ^ mem[i]) break;
if (i == len) return;
/* Reject builtin interesting values. */
if (len == 2) {
i = sizeof(interesting_16) >> 1;
while (i--)
if (*((u16*)mem) == interesting_16[i] ||
*((u16*)mem) == SWAP16(interesting_16[i])) return;
}
if (len == 4) {
i = sizeof(interesting_32) >> 2;
while (i--)
if (*((u32*)mem) == interesting_32[i] ||
*((u32*)mem) == SWAP32(interesting_32[i])) return;
}
/* Reject anything that matches existing extras. Do a case-insensitive
match. We optimize by exploiting the fact that extras[] are sorted
by size. */
for (i = 0; i < extras_cnt; i++)
if (extras[i].len >= len) break;
for (; i < extras_cnt && extras[i].len == len; i++)
if (!memcmp_nocase(extras[i].data, mem, len)) return;
/* Last but not least, check a_extras[] for matches. There are no
guarantees of a particular sort order. */
auto_changed = 1;
for (i = 0; i < a_extras_cnt; i++) {
if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) {
a_extras[i].hit_cnt++;
goto sort_a_extras;
}
}
/* At this point, looks like we're dealing with a new entry. So, let's
append it if we have room. Otherwise, let's randomly evict some other
entry from the bottom half of the list. */
if (a_extras_cnt < MAX_AUTO_EXTRAS) {
a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) *
sizeof(struct extra_data));
a_extras[a_extras_cnt].data = ck_memdup(mem, len);
a_extras[a_extras_cnt].len = len;
a_extras_cnt++;
} else {
i = MAX_AUTO_EXTRAS / 2 +
UR((MAX_AUTO_EXTRAS + 1) / 2);
ck_free(a_extras[i].data);
a_extras[i].data = ck_memdup(mem, len);
a_extras[i].len = len;
a_extras[i].hit_cnt = 0;
}
sort_a_extras:
/* First, sort all auto extras by use count, descending order. */
qsort(a_extras, a_extras_cnt, sizeof(struct extra_data),
compare_extras_use_d);
/* Then, sort the top USE_AUTO_EXTRAS entries by size. */
qsort(a_extras, MIN(USE_AUTO_EXTRAS, a_extras_cnt),
sizeof(struct extra_data), compare_extras_len);
}
/* Save automatically generated extras. */
static void save_auto(void) {
u32 i;
if (!auto_changed) return;
auto_changed = 0;
for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); i++) {
u8* fn = alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, i);
s32 fd;
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
ck_write(fd, a_extras[i].data, a_extras[i].len, fn);
close(fd);
ck_free(fn);
}
}
/* Load automatically generated extras. */
static void load_auto(void) {
u32 i;
for (i = 0; i < USE_AUTO_EXTRAS; i++) {
u8 tmp[MAX_AUTO_EXTRA + 1];
u8* fn = alloc_printf("%s/.state/auto_extras/auto_%06u", in_dir, i);
s32 fd, len;
fd = open(fn, O_RDONLY, 0600);
if (fd < 0) {
if (errno != ENOENT) PFATAL("Unable to open '%s'", fn);
ck_free(fn);
break;
}
/* We read one byte more to cheaply detect tokens that are too
long (and skip them). */
len = read(fd, tmp, MAX_AUTO_EXTRA + 1);
if (len < 0) PFATAL("Unable to read from '%s'", fn);
if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA)
maybe_add_auto(tmp, len);
close(fd);
ck_free(fn);
}
if (i) OKF("Loaded %u auto-discovered dictionary tokens.", i);
else OKF("No auto-generated dictionary tokens to reuse.");
}
/* Destroy extras. */
static void destroy_extras(void) {
u32 i;
for (i = 0; i < extras_cnt; i++)
ck_free(extras[i].data);
ck_free(extras);
for (i = 0; i < a_extras_cnt; i++)
ck_free(a_extras[i].data);
ck_free(a_extras);
}
/* Spin up fork server (instrumented mode only). The idea is explained here:
http://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html
In essence, the instrumentation allows us to skip execve(), and just keep
cloning a stopped child. So, we just execute once, and then send commands
through a pipe. The other part of this logic is in afl-as.h. */
EXP_ST void init_forkserver(char** argv) {
static struct itimerval it;
int st_pipe[2], ctl_pipe[2];
int status;
s32 rlen;
ACTF("Spinning up the fork server...");
if (pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed");
forksrv_pid = fork();
if (forksrv_pid < 0) PFATAL("fork() failed");
if (!forksrv_pid) {
struct rlimit r;
/* Umpf. On OpenBSD, the default fd limit for root users is set to
soft 128. Let's try to fix that... */
if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) {
r.rlim_cur = FORKSRV_FD + 2;
setrlimit(RLIMIT_NOFILE, &r); /* Ignore errors */
}
if (mem_limit) {
r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;
#ifdef RLIMIT_AS
setrlimit(RLIMIT_AS, &r); /* Ignore errors */
#else
/* This takes care of OpenBSD, which doesn't have RLIMIT_AS, but
according to reliable sources, RLIMIT_DATA covers anonymous
maps - so we should be getting good protection against OOM bugs. */
setrlimit(RLIMIT_DATA, &r); /* Ignore errors */
#endif /* ^RLIMIT_AS */
}
/* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered
before the dump is complete. */
r.rlim_max = r.rlim_cur = 0;
setrlimit(RLIMIT_CORE, &r); /* Ignore errors */
/* Isolate the process and configure standard descriptors. If out_file is
specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */
setsid();
dup2(dev_null_fd, 1);
dup2(dev_null_fd, 2);
if (out_file) {
dup2(dev_null_fd, 0);
} else {
dup2(out_fd, 0);
close(out_fd);
}
/* Set up control and status pipes, close the unneeded original fds. */
if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed");
if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed");
close(ctl_pipe[0]);
close(ctl_pipe[1]);
close(st_pipe[0]);
close(st_pipe[1]);
close(out_dir_fd);
close(dev_null_fd);
close(dev_urandom_fd);
close(fileno(plot_file));
/* This should improve performance a bit, since it stops the linker from
doing extra work post-fork(). */
if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0);
/* Set sane defaults for ASAN if nothing else specified. */
setenv("ASAN_OPTIONS", "abort_on_error=1:"
"detect_leaks=0:"
"symbolize=0:"
"allocator_may_return_null=1", 0);
/* MSAN is tricky, because it doesn't support abort_on_error=1 at this
point. So, we do this in a very hacky way. */
setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
"symbolize=0:"
"abort_on_error=1:"
"allocator_may_return_null=1:"
"msan_track_origins=0", 0);
execv(target_path, argv);
/* Use a distinctive bitmap signature to tell the parent about execv()
falling through. */
*(u32*)trace_bits = EXEC_FAIL_SIG;
exit(0);
}
/* Close the unneeded endpoints. */
close(ctl_pipe[0]);
close(st_pipe[1]);
fsrv_ctl_fd = ctl_pipe[1];
fsrv_st_fd = st_pipe[0];
/* Wait for the fork server to come up, but don't wait too long. */
it.it_value.tv_sec = ((exec_tmout * FORK_WAIT_MULT) / 1000);
it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000;
setitimer(ITIMER_REAL, &it, NULL);
rlen = read(fsrv_st_fd, &status, 4);
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &it, NULL);
/* If we have a four-byte "hello" message from the server, we're all set.
Otherwise, try to figure out what went wrong. */
if (rlen == 4) {
OKF("All right - fork server is up.");
return;
}
if (child_timed_out)
FATAL("Timeout while initializing fork server (adjusting -t may help)");
if (waitpid(forksrv_pid, &status, 0) <= 0)
PFATAL("waitpid() failed");
if (WIFSIGNALED(status)) {
if (mem_limit && mem_limit < 500 && uses_asan) {
SAYF("\n" cLRD "[-] " cRST
"Whoops, the target binary crashed suddenly, before receiving any input\n"
" from the fuzzer! Since it seems to be built with ASAN and you have a\n"
" restrictive memory limit configured, this is expected; please read\n"
" %s/notes_for_asan.txt for help.\n", doc_path);
} else if (!mem_limit) {
SAYF("\n" cLRD "[-] " cRST
"Whoops, the target binary crashed suddenly, before receiving any input\n"
" from the fuzzer! There are several probable explanations:\n\n"
" - The binary is just buggy and explodes entirely on its own. If so, you\n"
" need to fix the underlying problem or find a better replacement.\n\n"
#ifdef __APPLE__
" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"
#endif /* __APPLE__ */
" - Less likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");
} else {
SAYF("\n" cLRD "[-] " cRST
"Whoops, the target binary crashed suddenly, before receiving any input\n"
" from the fuzzer! There are several probable explanations:\n\n"
" - The current memory limit (%s) is too restrictive, causing the\n"
" target to hit an OOM condition in the dynamic linker. Try bumping up\n"
" the limit with the -m setting in the command line. A simple way confirm\n"
" this diagnosis would be:\n\n"
#ifdef RLIMIT_AS
" ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#else
" ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#endif /* ^RLIMIT_AS */
" Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
" estimate the required amount of virtual memory for the binary.\n\n"
" - The binary is just buggy and explodes entirely on its own. If so, you\n"
" need to fix the underlying problem or find a better replacement.\n\n"
#ifdef __APPLE__
" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"
#endif /* __APPLE__ */
" - Less likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
DMS(mem_limit << 20), mem_limit - 1);
}
FATAL("Fork server crashed with signal %d", WTERMSIG(status));
}
if (*(u32*)trace_bits == EXEC_FAIL_SIG)
FATAL("Unable to execute target application ('%s')", argv[0]);
if (mem_limit && mem_limit < 500 && uses_asan) {
SAYF("\n" cLRD "[-] " cRST
"Hmm, looks like the target binary terminated before we could complete a\n"
" handshake with the injected code. Since it seems to be built with ASAN and\n"
" you have a restrictive memory limit configured, this is expected; please\n"
" read %s/notes_for_asan.txt for help.\n", doc_path);
} else if (!mem_limit) {
SAYF("\n" cLRD "[-] " cRST
"Hmm, looks like the target binary terminated before we could complete a\n"
" handshake with the injected code. Perhaps there is a horrible bug in the\n"
" fuzzer. Poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");
} else {
SAYF("\n" cLRD "[-] " cRST
"Hmm, looks like the target binary terminated before we could complete a\n"
" handshake with the injected code. There are %s probable explanations:\n\n"
"%s"
" - The current memory limit (%s) is too restrictive, causing an OOM\n"
" fault in the dynamic linker. This can be fixed with the -m option. A\n"
" simple way to confirm the diagnosis may be:\n\n"
#ifdef RLIMIT_AS
" ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#else
" ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#endif /* ^RLIMIT_AS */
" Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
" estimate the required amount of virtual memory for the binary.\n\n"
" - Less likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
getenv(DEFER_ENV_VAR) ? "three" : "two",
getenv(DEFER_ENV_VAR) ?
" - You are using deferred forkserver, but __AFL_INIT() is never\n"
" reached before the program terminates.\n\n" : "",
DMS(mem_limit << 20), mem_limit - 1);
}
FATAL("Fork server handshake failed");
}
/* Execute target application, monitoring for timeouts. Return status
information. The called program will update trace_bits[]. */
static u8 run_target(char** argv, u32 timeout) {
static struct itimerval it;
static u32 prev_timed_out = 0;
static u64 exec_ms = 0;
int status = 0;
u32 tb4;
child_timed_out = 0;
/* After this memset, trace_bits[] are effectively volatile, so we
must prevent any earlier operations from venturing into that
territory. */
memset(trace_bits, 0, MAP_SIZE);
MEM_BARRIER();
/* If we're running in "dumb" mode, we can't rely on the fork server
logic compiled into the target program, so we will just keep calling
execve(). There is a bit of code duplication between here and
init_forkserver(), but c'est la vie. */
if (dumb_mode == 1 || no_forkserver) {
child_pid = fork();
if (child_pid < 0) PFATAL("fork() failed");
if (!child_pid) {
struct rlimit r;
if (mem_limit) {
r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;
#ifdef RLIMIT_AS
setrlimit(RLIMIT_AS, &r); /* Ignore errors */
#else
setrlimit(RLIMIT_DATA, &r); /* Ignore errors */
#endif /* ^RLIMIT_AS */
}
r.rlim_max = r.rlim_cur = 0;
setrlimit(RLIMIT_CORE, &r); /* Ignore errors */
/* Isolate the process and configure standard descriptors. If out_file is
specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */
setsid();
dup2(dev_null_fd, 1);
dup2(dev_null_fd, 2);
if (out_file) {
dup2(dev_null_fd, 0);
} else {
dup2(out_fd, 0);
close(out_fd);
}
/* On Linux, would be faster to use O_CLOEXEC. Maybe TODO. */
close(dev_null_fd);
close(out_dir_fd);
close(dev_urandom_fd);
close(fileno(plot_file));
/* Set sane defaults for ASAN if nothing else specified. */
setenv("ASAN_OPTIONS", "abort_on_error=1:"
"detect_leaks=0:"
"symbolize=0:"
"allocator_may_return_null=1", 0);
setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
"symbolize=0:"
"msan_track_origins=0", 0);
execv(target_path, argv);
/* Use a distinctive bitmap value to tell the parent about execv()
falling through. */
*(u32*)trace_bits = EXEC_FAIL_SIG;
exit(0);
}
} else {
s32 res;
/* In non-dumb mode, we have the fork server up and running, so simply
tell it to have at it, and then read back PID. */
if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) {
if (stop_soon) return 0;
RPFATAL(res, "Unable to request new process from fork server (OOM?)");
}
if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) {
if (stop_soon) return 0;
RPFATAL(res, "Unable to request new process from fork server (OOM?)");
}
if (child_pid <= 0) FATAL("Fork server is misbehaving (OOM?)");
}
/* Configure timeout, as requested by user, then wait for child to terminate. */
it.it_value.tv_sec = (timeout / 1000);
it.it_value.tv_usec = (timeout % 1000) * 1000;
setitimer(ITIMER_REAL, &it, NULL);
/* The SIGALRM handler simply kills the child_pid and sets child_timed_out. */
if (dumb_mode == 1 || no_forkserver) {
if (waitpid(child_pid, &status, 0) <= 0) PFATAL("waitpid() failed");
} else {
s32 res;
if ((res = read(fsrv_st_fd, &status, 4)) != 4) {
if (stop_soon) return 0;
RPFATAL(res, "Unable to communicate with fork server (OOM?)");
}
}
if (!WIFSTOPPED(status)) child_pid = 0;//这行代码检查子进程的状态。如果子进程没有被停止即没有收到停止信号则将child_pid变量设置为0。这通常意味着子进程已经完成执行。
getitimer(ITIMER_REAL, &it);//这行代码获取当前的实时定时器ITIMER_REAL的剩余时间并将其存储在it结构体中。
exec_ms = (u64) timeout - (it.it_value.tv_sec * 1000 +
it.it_value.tv_usec / 1000);
//这行代码计算被测试程序的实际执行时间。它从预设的超时时间timeout中减去定时器剩余的时间转换成毫秒。这个值被存储在exec_ms中代表执行时间单位是毫秒。
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;//定时器置零
setitimer(ITIMER_REAL, &it, NULL);//设置实时定时器使用上面设置的0值这意味着定时器被禁用不会再产生超时信号。
total_execs++;
/* Any subsequent operations on trace_bits must not be moved by the
compiler below this point. Past this location, trace_bits[] behave
very normally and do not have to be treated as volatile. */
MEM_BARRIER();//这是一个内存屏障函数,确保前面的操作不会被编译器重排到后面的操作之后。这对于多线程程序中的内存访问顺序很重要。
tb4 = *(u32*)trace_bits;//这行代码将trace_bits一个用于记录被测试程序执行路径的位图数组的前4个字节的内容读取到一个无符号32位整数tb4中。这个值可能用于后续的分析以确定被测试程序的执行路径是否产生了新的覆盖
#ifdef WORD_SIZE_64
classify_counts((u64*)trace_bits);
#else
classify_counts((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */
//这部分代码使用宏WORD_SIZE_64来确定系统是64位还是32位。根据系统的字长它将trace_bits转换为相应的指针类型u64*或u32*并调用classify_counts函数。这个函数可能用于对trace_bits中的计数数据进行分类或简化。
prev_timed_out = child_timed_out;
/* Report outcome to caller. */
if (WIFSIGNALED(status) && !stop_soon) {
//这部分代码检查子进程是否因信号而终止。如果子进程因超时被杀死SIGKILL则返回FAULT_TMOUT。否则返回FAULT_CRASH表示子进程崩溃。
kill_signal = WTERMSIG(status);
if (child_timed_out && kill_signal == SIGKILL) return FAULT_TMOUT;
return FAULT_CRASH;
}
/* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and
must use a special exit code. */
if (uses_asan && WEXITSTATUS(status) == MSAN_ERROR) {
//对于使用地址sanitizerASAN的程序如果退出状态表明发生了内存错误MSAN_ERROR则返回FAULT_CRASH
kill_signal = 0;
return FAULT_CRASH;
}
if ((dumb_mode == 1 || no_forkserver) && tb4 == EXEC_FAIL_SIG)
//如果运行在“dumb”模式或没有使用fork服务器且tb4的值表明执行失败则返回FAULT_ERROR。
return FAULT_ERROR;
/* It makes sense to account for the slowest units only if the testcase was run
under the user defined timeout. */
if (!(timeout > exec_tmout) && (slowest_exec_ms < exec_ms)) {
//如果测试用例在用户定义的超时时间内运行并且其执行时间是目前为止最慢的则更新slowest_exec_ms。
slowest_exec_ms = exec_ms;
}
return FAULT_NONE;
}
/* Write modified data to file for testing. If out_file is set, the old file
is unlinked and a new one is created. Otherwise, out_fd is rewound and
truncated. */
static void write_to_testcase(void* mem, u32 len) {
s32 fd = out_fd;
if (out_file) {
//如果指定了输出文件名out_file则删除旧文件并创建一个新文件。否则将文件描述符的位置设置为文件开头。
unlink(out_file); /* Ignore errors. */
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", out_file);
} else lseek(fd, 0, SEEK_SET);
//如果没有指定输出文件名,则截断文件到指定长度并重置文件位置。如果指定了输出文件名,则关闭文件描述符。
ck_write(fd, mem, len, out_file);
if (!out_file) {
if (ftruncate(fd, len)) PFATAL("ftruncate() failed");
lseek(fd, 0, SEEK_SET);
} else close(fd);
}
/* The same, but with an adjustable gap. Used for trimming. */
static void write_with_gap(void* mem, u32 len, u32 skip_at, u32 skip_len) {
//这个函数类似于write_to_testcase但允许在文件中跳过一段数据skip_at和skip_len
s32 fd = out_fd;
u32 tail_len = len - skip_at - skip_len;
if (out_file) {
//如果指定了输出文件名,则删除旧文件并创建一个新文件。否则,将文件描述符的位置设置为文件开头。
unlink(out_file); /* Ignore errors. */
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", out_file);
} else lseek(fd, 0, SEEK_SET);
if (skip_at) ck_write(fd, mem, skip_at, out_file);
if (tail_len) ck_write(fd, mem + skip_at + skip_len, tail_len, out_file);//写入跳过部分之后的数据
if (!out_file) {
//如果没有指定输出文件名,则截断文件到指定长度并重置文件位置。如果指定了输出文件名,则关闭文件描述符。
if (ftruncate(fd, len - skip_len)) PFATAL("ftruncate() failed");
lseek(fd, 0, SEEK_SET);
} else close(fd);
}
static void show_stats(void);
/* Calibrate a new test case. This is done when processing the input directory
to warn about flaky or otherwise problematic test cases early on; and when
new paths are discovered to detect variable behavior and so on. */
//start_us, stop_us;
//保存了一些旧的状态值,以便在函数执行完毕后恢复。
static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem,
u32 handicap, u8 from_queue) {
static u8 first_trace[MAP_SIZE];
u8 fault = 0, new_bits = 0, var_detected = 0, hnb = 0,
first_run = (q->exec_cksum == 0);
u64 start_us, stop_us;
s32 old_sc = stage_cur, old_sm = stage_max;
u32 use_tmout = exec_tmout;
u8* old_sn = stage_name;
/* Be a bit more generous about timeouts when resuming sessions, or when
trying to calibrate already-added finds. This helps avoid trouble due
to intermittent latency. */
//如果是在恢复会话或校准已经添加的测试用例时,会放宽超时限制。
if (!from_queue || resuming_fuzz)
use_tmout = MAX(exec_tmout + CAL_TMOUT_ADD,
exec_tmout * CAL_TMOUT_PERC / 100);
q->cal_failed++;
//设置当前阶段为“calibration”并根据是否快速校准设置阶段最大值。
stage_name = "calibration";
stage_max = fast_cal ? 3 : CAL_CYCLES;
/* Make sure the forkserver is up before we do anything, and let's not
count its spin-up time toward binary calibration. */
//如果没有运行在“dumb”模式并且没有使用fork服务器那么初始化fork服务器。
if (dumb_mode != 1 && !no_forkserver && !forksrv_pid)
init_forkserver(argv);
if (q->exec_cksum) {
//如果队列条目已经有执行校验和那么将第一次执行的trace bits复制到first_trace。
memcpy(first_trace, trace_bits, MAP_SIZE);
hnb = has_new_bits(virgin_bits);
if (hnb > new_bits) new_bits = hnb;
}
start_us = get_cur_time_us();//计时
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
u32 cksum;
if (!first_run && !(stage_cur % stats_update_freq)) show_stats();
//如果不是第一次运行并且到了更新频率,显示统计信息。
write_to_testcase(use_mem, q->len);//将测试用例数据写入文件。
fault = run_target(argv, use_tmout);//运行目标程序并获取执行结果。
/* stop_soon is set by the handler for Ctrl+C. When it's pressed,
we want to bail out quickly. */
if (stop_soon || fault != crash_mode) goto abort_calibration;//如果用户请求停止或者执行结果不是崩溃模式,则跳到校准中止。
if (!dumb_mode && !stage_cur && !count_bytes(trace_bits)) {
//如果不是“dumb”模式并且是第一次校准并且没有新的代码覆盖则设置错误为FAULT_NOINST并跳到校准中止。
fault = FAULT_NOINST;
goto abort_calibration;
}
cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);//计算当前trace bits的哈希值并与队列条目的执行校验和比较。
if (q->exec_cksum != cksum) {
//如果有新的bits被设置则更新new_bits。
hnb = has_new_bits(virgin_bits);
if (hnb > new_bits) new_bits = hnb;
if (q->exec_cksum) {//如果队列条目之前有执行校验和,则检查变量字节。
u32 i;
//对于每个不同的字节,将其标记为变量字节,并增加校准周期。
for (i = 0; i < MAP_SIZE; i++) {
if (!var_bytes[i] && first_trace[i] != trace_bits[i]) {
var_bytes[i] = 1;
stage_max = CAL_CYCLES_LONG;
}
}
var_detected = 1;
//如果队列条目之前没有执行校验和则更新其执行校验和并复制当前trace bits。
} else {
q->exec_cksum = cksum;
memcpy(first_trace, trace_bits, MAP_SIZE);
}
}
}
stop_us = get_cur_time_us();
total_cal_us += stop_us - start_us;//更新总校准时间和周期。
total_cal_cycles += stage_max;
/* OK, let's collect some stats about the performance of this test case.
This is used for fuzzing air time calculations in calculate_score(). */
//更新队列条目的执行时间、位图大小和校准失败计数。
q->exec_us = (stop_us - start_us) / stage_max;
q->bitmap_size = count_bytes(trace_bits);
q->handicap = handicap;
q->cal_failed = 0;
total_bitmap_size += q->bitmap_size;//更新总位图大小和条目数
total_bitmap_entries++;
update_bitmap_score(q);
/* If this case didn't result in new output from the instrumentation, tell
parent. This is a non-critical problem, but something to warn the user
about. */
if (!dumb_mode && first_run && !fault && !new_bits) fault = FAULT_NOBITS;
abort_calibration:
//如果有新的bits被设置并且队列条目之前没有新覆盖则更新队列条目的新覆盖标志。
if (new_bits == 2 && !q->has_new_cov) {
q->has_new_cov = 1;
queued_with_cov++;
}
/* Mark variable paths. */
if (var_detected) {
//如果检测到变量行为,则标记队列条目。
var_byte_count = count_bytes(var_bytes);
if (!q->var_behavior) {
mark_as_variable(q);
queued_variable++;
}
}
//恢复旧的状态值。
stage_name = old_sn;
stage_cur = old_sc;
stage_max = old_sm;
if (!first_run) show_stats();//如果校准中止,则跳到函数末尾。
return fault;
}
/* Examine map coverage. Called once, for first test case. */
static void check_map_coverage(void) {
/*
这个函数检查映射覆盖率是否足够。如果映射中被设置的字节数少于100则函数直接返回。
然后,它检查映射的后半部分是否有任何被设置的位。如果没有,这意味着测试用例只触发了
映射的前半部分,这可能意味着二进制文件需要重新编译以获得更好的覆盖率。在这种情况下
,它会打印一条警告消息。
*/
u32 i;
if (count_bytes(trace_bits) < 100) return;
for (i = (1 << (MAP_SIZE_POW2 - 1)); i < MAP_SIZE; i++)
if (trace_bits[i]) return;
WARNF("Recompile binary with newer version of afl to improve coverage!");
}
/* Perform dry run of all test cases to confirm that the app is working as
expected. This is done only for the initial inputs, and only once. */
static void perform_dry_run(char** argv) {
struct queue_entry* q = queue;
u32 cal_failures = 0;
u8* skip_crashes = getenv("AFL_SKIP_CRASHES");
while (q) {
u8* use_mem;
u8 res;
s32 fd;
u8* fn = strrchr(q->fname, '/') + 1;
//打开并读取测试用例文件。
ACTF("Attempting dry run with '%s'...", fn);
fd = open(q->fname, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", q->fname);
use_mem = ck_alloc_nozero(q->len);////分配内存并读取测试用例数据。
if (read(fd, use_mem, q->len) != q->len)
FATAL("Short read from '%s'", q->fname);
close(fd);
res = calibrate_case(argv, q, use_mem, 0, 1);//调用calibrate_case函数来校准测试用例。
ck_free(use_mem);
if (stop_soon) return;
if (res == crash_mode || res == FAULT_NOBITS)
SAYF(cGRA " len = %u, map size = %u, exec speed = %llu us\n" cRST,
q->len, q->bitmap_size, q->exec_us);
switch (res) {
case FAULT_NONE:
if (q == queue) check_map_coverage();
if (crash_mode) FATAL("Test case '%s' does *NOT* crash", fn);
break;
case FAULT_TMOUT:
if (timeout_given) {
/* The -t nn+ syntax in the command line sets timeout_given to '2' and
instructs afl-fuzz to tolerate but skip queue entries that time
out. */
if (timeout_given > 1) {
WARNF("Test case results in a timeout (skipping)");
q->cal_failed = CAL_CHANCES;
cal_failures++;
break;
}
SAYF("\n" cLRD "[-] " cRST
"The program took more than %u ms to process one of the initial test cases.\n"
" Usually, the right thing to do is to relax the -t option - or to delete it\n"
" altogether and allow the fuzzer to auto-calibrate. That said, if you know\n"
" what you are doing and want to simply skip the unruly test cases, append\n"
" '+' at the end of the value passed to -t ('-t %u+').\n", exec_tmout,
exec_tmout);
FATAL("Test case '%s' results in a timeout", fn);
} else {
SAYF("\n" cLRD "[-] " cRST
"The program took more than %u ms to process one of the initial test cases.\n"
" This is bad news; raising the limit with the -t option is possible, but\n"
" will probably make the fuzzing process extremely slow.\n\n"
" If this test case is just a fluke, the other option is to just avoid it\n"
" altogether, and find one that is less of a CPU hog.\n", exec_tmout);
FATAL("Test case '%s' results in a timeout", fn);
}
case FAULT_CRASH:
if (crash_mode) break;
if (skip_crashes) {
WARNF("Test case results in a crash (skipping)");
q->cal_failed = CAL_CHANCES;
cal_failures++;
break;
}
if (mem_limit) {
SAYF("\n" cLRD "[-] " cRST
"Oops, the program crashed with one of the test cases provided. There are\n"
" several possible explanations:\n\n"
" - The test case causes known crashes under normal working conditions. If\n"
" so, please remove it. The fuzzer should be seeded with interesting\n"
" inputs - but not ones that cause an outright crash.\n\n"
" - The current memory limit (%s) is too low for this program, causing\n"
" it to die due to OOM when parsing valid files. To fix this, try\n"
" bumping it up with the -m setting in the command line. If in doubt,\n"
" try something along the lines of:\n\n"
/*
根据校准结果,执行不同的操作:
如果测试用例没有引起崩溃,并且不是在寻找崩溃的模式下运行,则检查映射覆盖率。
如果测试用例导致超时根据timeout_given变量的值可能跳过该测试用例或报告错误。
如果测试用例导致崩溃根据crash_mode变量的值和环境变量AFL_SKIP_CRASHES可能跳过该测试用例或报告错误。
*/
#ifdef RLIMIT_AS
" ( ulimit -Sv $[%llu << 10]; /path/to/binary [...] <testcase )\n\n"
#else
" ( ulimit -Sd $[%llu << 10]; /path/to/binary [...] <testcase )\n\n"
#endif /* ^RLIMIT_AS */
" Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
" estimate the required amount of virtual memory for the binary. Also,\n"
" if you are using ASAN, see %s/notes_for_asan.txt.\n\n"
#ifdef __APPLE__
" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"
#endif /* __APPLE__ */
" - Least likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
DMS(mem_limit << 20), mem_limit - 1, doc_path);
} else {
SAYF("\n" cLRD "[-] " cRST
"Oops, the program crashed with one of the test cases provided. There are\n"
" several possible explanations:\n\n"
" - The test case causes known crashes under normal working conditions. If\n"
" so, please remove it. The fuzzer should be seeded with interesting\n"
" inputs - but not ones that cause an outright crash.\n\n"
#ifdef __APPLE__
" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"
#endif /* __APPLE__ */
" - Least likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");
}
FATAL("Test case '%s' results in a crash", fn);
case FAULT_ERROR:
FATAL("Unable to execute target application ('%s')", argv[0]);
case FAULT_NOINST:
FATAL("No instrumentation detected");
case FAULT_NOBITS:
useless_at_start++;
if (!in_bitmap && !shuffle_queue)
WARNF("No new instrumentation output, test case may be useless.");
break;
}
if (q->var_behavior) WARNF("Instrumentation output varies across runs.");
q = q->next;
}
if (cal_failures) {
if (cal_failures == queued_paths)
FATAL("All test cases time out%s, giving up!",
skip_crashes ? " or crash" : "");
WARNF("Skipped %u test cases (%0.02f%%) due to timeouts%s.", cal_failures,
((double)cal_failures) * 100 / queued_paths,
skip_crashes ? " or crashes" : "");
if (cal_failures * 5 > queued_paths)
WARNF(cLRD "High percentage of rejected test cases, check settings!");
}
OKF("All test cases processed.");
}
/* Helper function: link() if possible, copy otherwise. */
static void link_or_copy(u8* old_path, u8* new_path) {
s32 i = link(old_path, new_path);
s32 sfd, dfd;
u8* tmp;
if (!i) return;
sfd = open(old_path, O_RDONLY);
if (sfd < 0) PFATAL("Unable to open '%s'", old_path);
dfd = open(new_path, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (dfd < 0) PFATAL("Unable to create '%s'", new_path);
tmp = ck_alloc(64 * 1024);
while ((i = read(sfd, tmp, 64 * 1024)) > 0)
ck_write(dfd, tmp, i, new_path);
if (i < 0) PFATAL("read() failed");
ck_free(tmp);
close(sfd);
close(dfd);
}
static void nuke_resume_dir(void);
/* Create hard links for input test cases in the output directory, choosing
good names and pivoting accordingly. */
static void pivot_inputs(void) {
struct queue_entry* q = queue;
u32 id = 0;
ACTF("Creating hard links for all input files...");
while (q) {
u8 *nfn, *rsl = strrchr(q->fname, '/');
u32 orig_id;
if (!rsl) rsl = q->fname; else rsl++;
/* If the original file name conforms to the syntax and the recorded
ID matches the one we'd assign, just use the original file name.
This is valuable for resuming fuzzing runs. */
#ifndef SIMPLE_FILES
# define CASE_PREFIX "id:"
#else
# define CASE_PREFIX "id_"
#endif /* ^!SIMPLE_FILES */
if (!strncmp(rsl, CASE_PREFIX, 3) &&
sscanf(rsl + 3, "%06u", &orig_id) == 1 && orig_id == id) {
u8* src_str;
u32 src_id;
resuming_fuzz = 1;
nfn = alloc_printf("%s/queue/%s", out_dir, rsl);
/* Since we're at it, let's also try to find parent and figure out the
appropriate depth for this entry. */
src_str = strchr(rsl + 3, ':');
if (src_str && sscanf(src_str + 1, "%06u", &src_id) == 1) {
struct queue_entry* s = queue;
while (src_id-- && s) s = s->next;
if (s) q->depth = s->depth + 1;
if (max_depth < q->depth) max_depth = q->depth;
}
} else {
/* No dice - invent a new name, capturing the original one as a
substring. */
#ifndef SIMPLE_FILES
u8* use_name = strstr(rsl, ",orig:");
if (use_name) use_name += 6; else use_name = rsl;
nfn = alloc_printf("%s/queue/id:%06u,orig:%s", out_dir, id, use_name);
#else
nfn = alloc_printf("%s/queue/id_%06u", out_dir, id);
#endif /* ^!SIMPLE_FILES */
}
/* Pivot to the new queue entry. */
link_or_copy(q->fname, nfn);
ck_free(q->fname);
q->fname = nfn;
/* Make sure that the passed_det value carries over, too. */
if (q->passed_det) mark_as_det_done(q);
q = q->next;
id++;
}
if (in_place_resume) nuke_resume_dir();
}
#ifndef SIMPLE_FILES
/* Construct a file name for a new test case, capturing the operation
that led to its discovery. Uses a static buffer. */
static u8* describe_op(u8 hnb) {
static u8 ret[256];
if (syncing_party) {
sprintf(ret, "sync:%s,src:%06u", syncing_party, syncing_case);
} else {
sprintf(ret, "src:%06u", current_entry);
if (splicing_with >= 0)
sprintf(ret + strlen(ret), "+%06u", splicing_with);
sprintf(ret + strlen(ret), ",op:%s", stage_short);
if (stage_cur_byte >= 0) {
sprintf(ret + strlen(ret), ",pos:%u", stage_cur_byte);
if (stage_val_type != STAGE_VAL_NONE)
sprintf(ret + strlen(ret), ",val:%s%+d",
(stage_val_type == STAGE_VAL_BE) ? "be:" : "",
stage_cur_val);
} else sprintf(ret + strlen(ret), ",rep:%u", stage_cur_val);
}
if (hnb == 2) strcat(ret, ",+cov");
return ret;
}
#endif /* !SIMPLE_FILES */
/* Write a message accompanying the crash directory :-) */
static void write_crash_readme(void) {
u8* fn = alloc_printf("%s/crashes/README.txt", out_dir);
s32 fd;
FILE* f;
fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
ck_free(fn);
/* Do not die on errors here - that would be impolite. */
if (fd < 0) return;
f = fdopen(fd, "w");
if (!f) {
close(fd);
return;
}
fprintf(f, "Command line used to find this crash:\n\n"
"%s\n\n"
"If you can't reproduce a bug outside of afl-fuzz, be sure to set the same\n"
"memory limit. The limit used for this fuzzing session was %s.\n\n"
"Need a tool to minimize test cases before investigating the crashes or sending\n"
"them to a vendor? Check out the afl-tmin that comes with the fuzzer!\n\n"
"Found any cool bugs in open-source tools using afl-fuzz? If yes, please drop\n"
"me a mail at <lcamtuf@coredump.cx> once the issues are fixed - I'd love to\n"
"add your finds to the gallery at:\n\n"
" http://lcamtuf.coredump.cx/afl/\n\n"
"Thanks :-)\n",
orig_cmdline, DMS(mem_limit << 20)); /* ignore errors */
fclose(f);
}
/* Check if the result of an execve() during routine fuzzing is interesting,
save or queue the input test case for further analysis if so. Returns 1 if
entry is saved, 0 otherwise. */
static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) {
u8 *fn = "";
u8 hnb;
s32 fd;
u8 keeping = 0, res;
if (fault == crash_mode) {
/* Keep only if there are new bits in the map, add to queue for
future fuzzing, etc. */
if (!(hnb = has_new_bits(virgin_bits))) {
if (crash_mode) total_crashes++;
return 0;
}
#ifndef SIMPLE_FILES
fn = alloc_printf("%s/queue/id:%06u,%s", out_dir, queued_paths,
describe_op(hnb));
#else
fn = alloc_printf("%s/queue/id_%06u", out_dir, queued_paths);
#endif /* ^!SIMPLE_FILES */
add_to_queue(fn, len, 0);
if (hnb == 2) {
queue_top->has_new_cov = 1;
queued_with_cov++;
}
queue_top->exec_cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
/* Try to calibrate inline; this also calls update_bitmap_score() when
successful. */
res = calibrate_case(argv, queue_top, mem, queue_cycle - 1, 0);
if (res == FAULT_ERROR)
FATAL("Unable to execute target application");
fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
ck_write(fd, mem, len, fn);
close(fd);
keeping = 1;
}
switch (fault) {
case FAULT_TMOUT:
/* Timeouts are not very interesting, but we're still obliged to keep
a handful of samples. We use the presence of new bits in the
hang-specific bitmap as a signal of uniqueness. In "dumb" mode, we
just keep everything. */
total_tmouts++;
if (unique_hangs >= KEEP_UNIQUE_HANG) return keeping;
if (!dumb_mode) {
#ifdef WORD_SIZE_64
simplify_trace((u64*)trace_bits);
#else
simplify_trace((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */
if (!has_new_bits(virgin_tmout)) return keeping;
}
unique_tmouts++;
/* Before saving, we make sure that it's a genuine hang by re-running
the target with a more generous timeout (unless the default timeout
is already generous). */
if (exec_tmout < hang_tmout) {
u8 new_fault;
write_to_testcase(mem, len);
new_fault = run_target(argv, hang_tmout);
/* A corner case that one user reported bumping into: increasing the
timeout actually uncovers a crash. Make sure we don't discard it if
so. */
if (!stop_soon && new_fault == FAULT_CRASH) goto keep_as_crash;
if (stop_soon || new_fault != FAULT_TMOUT) return keeping;
}
#ifndef SIMPLE_FILES
fn = alloc_printf("%s/hangs/id:%06llu,%s", out_dir,
unique_hangs, describe_op(0));
#else
fn = alloc_printf("%s/hangs/id_%06llu", out_dir,
unique_hangs);
#endif /* ^!SIMPLE_FILES */
unique_hangs++;
last_hang_time = get_cur_time();
break;
case FAULT_CRASH:
keep_as_crash:
/* This is handled in a manner roughly similar to timeouts,
except for slightly different limits and no need to re-run test
cases. */
total_crashes++;
if (unique_crashes >= KEEP_UNIQUE_CRASH) return keeping;
if (!dumb_mode) {
#ifdef WORD_SIZE_64
simplify_trace((u64*)trace_bits);
#else
simplify_trace((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */
if (!has_new_bits(virgin_crash)) return keeping;
}
if (!unique_crashes) write_crash_readme();
#ifndef SIMPLE_FILES
fn = alloc_printf("%s/crashes/id:%06llu,sig:%02u,%s", out_dir,
unique_crashes, kill_signal, describe_op(0));
#else
fn = alloc_printf("%s/crashes/id_%06llu_%02u", out_dir, unique_crashes,
kill_signal);
#endif /* ^!SIMPLE_FILES */
unique_crashes++;
last_crash_time = get_cur_time();
last_crash_execs = total_execs;
break;
case FAULT_ERROR: FATAL("Unable to execute target application");
default: return keeping;
}
/* If we're here, we apparently want to save the crash or hang
test case, too. */
fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
ck_write(fd, mem, len, fn);
close(fd);
ck_free(fn);
return keeping;
}
/* When resuming, try to find the queue position to start from. This makes sense
only when resuming, and when we can find the original fuzzer_stats. */
static u32 find_start_position(void) {
static u8 tmp[4096]; /* Ought to be enough for anybody. */
u8 *fn, *off;
s32 fd, i;
u32 ret;
if (!resuming_fuzz) return 0;
if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir);
else fn = alloc_printf("%s/../fuzzer_stats", in_dir);
fd = open(fn, O_RDONLY);
ck_free(fn);
if (fd < 0) return 0;
i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */
close(fd);
off = strstr(tmp, "cur_path : ");
if (!off) return 0;
ret = atoi(off + 20);
if (ret >= queued_paths) ret = 0;
return ret;
}
/* The same, but for timeouts. The idea is that when resuming sessions without
-t given, we don't want to keep auto-scaling the timeout over and over
again to prevent it from growing due to random flukes. */
static void find_timeout(void) {
static u8 tmp[4096]; /* Ought to be enough for anybody. */
u8 *fn, *off;
s32 fd, i;
u32 ret;
if (!resuming_fuzz) return;
if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir);
else fn = alloc_printf("%s/../fuzzer_stats", in_dir);
fd = open(fn, O_RDONLY);
ck_free(fn);
if (fd < 0) return;
i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */
close(fd);
off = strstr(tmp, "exec_timeout : ");
if (!off) return;
ret = atoi(off + 20);
if (ret <= 4) return;
exec_tmout = ret;
timeout_given = 3;
}
/* Update stats file for unattended monitoring. */
static void write_stats_file(double bitmap_cvg, double stability, double eps) {
static double last_bcvg, last_stab, last_eps;
static struct rusage usage;
u8* fn = alloc_printf("%s/fuzzer_stats", out_dir);
s32 fd;
FILE* f;
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
ck_free(fn);
f = fdopen(fd, "w");
if (!f) PFATAL("fdopen() failed");
/* Keep last values in case we're called from another context
where exec/sec stats and such are not readily available. */
if (!bitmap_cvg && !stability && !eps) {
bitmap_cvg = last_bcvg;
stability = last_stab;
eps = last_eps;
} else {
last_bcvg = bitmap_cvg;
last_stab = stability;
last_eps = eps;
}
fprintf(f, "start_time : %llu\n"
"last_update : %llu\n"
"fuzzer_pid : %u\n"
"cycles_done : %llu\n"
"execs_done : %llu\n"
"execs_per_sec : %0.02f\n"
"paths_total : %u\n"
"paths_favored : %u\n"
"paths_found : %u\n"
"paths_imported : %u\n"
"max_depth : %u\n"
"cur_path : %u\n" /* Must match find_start_position() */
"pending_favs : %u\n"
"pending_total : %u\n"
"variable_paths : %u\n"
"stability : %0.02f%%\n"
"bitmap_cvg : %0.02f%%\n"
"unique_crashes : %llu\n"
"unique_hangs : %llu\n"
"last_path : %llu\n"
"last_crash : %llu\n"
"last_hang : %llu\n"
"execs_since_crash : %llu\n"
"exec_timeout : %u\n" /* Must match find_timeout() */
"afl_banner : %s\n"
"afl_version : " VERSION "\n"
"target_mode : %s%s%s%s%s%s%s\n"
"command_line : %s\n"
"slowest_exec_ms : %llu\n",
start_time / 1000, get_cur_time() / 1000, getpid(),
queue_cycle ? (queue_cycle - 1) : 0, total_execs, eps,
queued_paths, queued_favored, queued_discovered, queued_imported,
max_depth, current_entry, pending_favored, pending_not_fuzzed,
queued_variable, stability, bitmap_cvg, unique_crashes,
unique_hangs, last_path_time / 1000, last_crash_time / 1000,
last_hang_time / 1000, total_execs - last_crash_execs,
exec_tmout, use_banner,
qemu_mode ? "qemu " : "", dumb_mode ? " dumb " : "",
no_forkserver ? "no_forksrv " : "", crash_mode ? "crash " : "",
persistent_mode ? "persistent " : "", deferred_mode ? "deferred " : "",
(qemu_mode || dumb_mode || no_forkserver || crash_mode ||
persistent_mode || deferred_mode) ? "" : "default",
orig_cmdline, slowest_exec_ms);
/* ignore errors */
/* Get rss value from the children
We must have killed the forkserver process and called waitpid
before calling getrusage */
if (getrusage(RUSAGE_CHILDREN, &usage)) {
WARNF("getrusage failed");
} else if (usage.ru_maxrss == 0) {
fprintf(f, "peak_rss_mb : not available while afl is running\n");
} else {
#ifdef __APPLE__
fprintf(f, "peak_rss_mb : %zu\n", usage.ru_maxrss >> 20);
#else
fprintf(f, "peak_rss_mb : %zu\n", usage.ru_maxrss >> 10);
#endif /* ^__APPLE__ */
}
fclose(f);
}
/* Update the plot file if there is a reason to. */
static void maybe_update_plot_file(double bitmap_cvg, double eps) {
static u32 prev_qp, prev_pf, prev_pnf, prev_ce, prev_md;
static u64 prev_qc, prev_uc, prev_uh;
if (prev_qp == queued_paths && prev_pf == pending_favored &&
prev_pnf == pending_not_fuzzed && prev_ce == current_entry &&
prev_qc == queue_cycle && prev_uc == unique_crashes &&
prev_uh == unique_hangs && prev_md == max_depth) return;
prev_qp = queued_paths;
prev_pf = pending_favored;
prev_pnf = pending_not_fuzzed;
prev_ce = current_entry;
prev_qc = queue_cycle;
prev_uc = unique_crashes;
prev_uh = unique_hangs;
prev_md = max_depth;
/* Fields in the file:
unix_time, cycles_done, cur_path, paths_total, paths_not_fuzzed,
favored_not_fuzzed, unique_crashes, unique_hangs, max_depth,
execs_per_sec */
fprintf(plot_file,
"%llu, %llu, %u, %u, %u, %u, %0.02f%%, %llu, %llu, %u, %0.02f\n",
get_cur_time() / 1000, queue_cycle - 1, current_entry, queued_paths,
pending_not_fuzzed, pending_favored, bitmap_cvg, unique_crashes,
unique_hangs, max_depth, eps); /* ignore errors */
fflush(plot_file);
}
/* A helper function for maybe_delete_out_dir(), deleting all prefixed
files in a directory. */
static u8 delete_files(u8* path, u8* prefix) {
DIR* d;
struct dirent* d_ent;
d = opendir(path);
if (!d) return 0;
while ((d_ent = readdir(d))) {
if (d_ent->d_name[0] != '.' && (!prefix ||
!strncmp(d_ent->d_name, prefix, strlen(prefix)))) {
u8* fname = alloc_printf("%s/%s", path, d_ent->d_name);
if (unlink(fname)) PFATAL("Unable to delete '%s'", fname);
ck_free(fname);
}
}
closedir(d);
return !!rmdir(path);
}
/* Get the number of runnable processes, with some simple smoothing. */
static double get_runnable_processes(void) {
static double res;
#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__)
/* I don't see any portable sysctl or so that would quickly give us the
number of runnable processes; the 1-minute load average can be a
semi-decent approximation, though. */
if (getloadavg(&res, 1) != 1) return 0;
#else
/* On Linux, /proc/stat is probably the best way; load averages are
computed in funny ways and sometimes don't reflect extremely short-lived
processes well. */
FILE* f = fopen("/proc/stat", "r");
u8 tmp[1024];
u32 val = 0;
if (!f) return 0;
while (fgets(tmp, sizeof(tmp), f)) {
if (!strncmp(tmp, "procs_running ", 14) ||
!strncmp(tmp, "procs_blocked ", 14)) val += atoi(tmp + 14);
}
fclose(f);
if (!res) {
res = val;
} else {
res = res * (1.0 - 1.0 / AVG_SMOOTHING) +
((double)val) * (1.0 / AVG_SMOOTHING);
}
#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */
return res;
}
/* Delete the temporary directory used for in-place session resume. */
static void nuke_resume_dir(void) {
u8* fn;
fn = alloc_printf("%s/_resume/.state/deterministic_done", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/_resume/.state/auto_extras", out_dir);
if (delete_files(fn, "auto_")) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/_resume/.state/redundant_edges", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/_resume/.state/variable_behavior", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/_resume/.state", out_dir);
if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/_resume", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
return;
dir_cleanup_failed:
FATAL("_resume directory cleanup failed");
}
/* Delete fuzzer output directory if we recognize it as ours, if the fuzzer
is not currently running, and if the last run time isn't too great. */
static void maybe_delete_out_dir(void) {
FILE* f;
u8 *fn = alloc_printf("%s/fuzzer_stats", out_dir);
/* See if the output directory is locked. If yes, bail out. If not,
create a lock that will persist for the lifetime of the process
(this requires leaving the descriptor open).*/
out_dir_fd = open(out_dir, O_RDONLY);
if (out_dir_fd < 0) PFATAL("Unable to open '%s'", out_dir);
#ifndef __sun
if (flock(out_dir_fd, LOCK_EX | LOCK_NB) && errno == EWOULDBLOCK) {
SAYF("\n" cLRD "[-] " cRST
"Looks like the job output directory is being actively used by another\n"
" instance of afl-fuzz. You will need to choose a different %s\n"
" or stop the other process first.\n",
sync_id ? "fuzzer ID" : "output location");
FATAL("Directory '%s' is in use", out_dir);
}
#endif /* !__sun */
f = fopen(fn, "r");
if (f) {
u64 start_time, last_update;
if (fscanf(f, "start_time : %llu\n"
"last_update : %llu\n", &start_time, &last_update) != 2)
FATAL("Malformed data in '%s'", fn);
fclose(f);
/* Let's see how much work is at stake. */
if (!in_place_resume && last_update - start_time > OUTPUT_GRACE * 60) {
SAYF("\n" cLRD "[-] " cRST
"The job output directory already exists and contains the results of more\n"
" than %u minutes worth of fuzzing. To avoid data loss, afl-fuzz will *NOT*\n"
" automatically delete this data for you.\n\n"
" If you wish to start a new session, remove or rename the directory manually,\n"
" or specify a different output location for this job. To resume the old\n"
" session, put '-' as the input directory in the command line ('-i -') and\n"
" try again.\n", OUTPUT_GRACE);
FATAL("At-risk data found in '%s'", out_dir);
}
}
ck_free(fn);
/* The idea for in-place resume is pretty simple: we temporarily move the old
queue/ to a new location that gets deleted once import to the new queue/
is finished. If _resume/ already exists, the current queue/ may be
incomplete due to an earlier abort, so we want to use the old _resume/
dir instead, and we let rename() fail silently. */
if (in_place_resume) {
u8* orig_q = alloc_printf("%s/queue", out_dir);
in_dir = alloc_printf("%s/_resume", out_dir);
rename(orig_q, in_dir); /* Ignore errors */
OKF("Output directory exists, will attempt session resume.");
ck_free(orig_q);
} else {
OKF("Output directory exists but deemed OK to reuse.");
}
ACTF("Deleting old session data...");
/* Okay, let's get the ball rolling! First, we need to get rid of the entries
in <out_dir>/.synced/.../id:*, if any are present. */
if (!in_place_resume) {
fn = alloc_printf("%s/.synced", out_dir);
if (delete_files(fn, NULL)) goto dir_cleanup_failed;
ck_free(fn);
}
/* Next, we need to clean up <out_dir>/queue/.state/ subdirectories: */
fn = alloc_printf("%s/queue/.state/deterministic_done", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/queue/.state/auto_extras", out_dir);
if (delete_files(fn, "auto_")) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/queue/.state/redundant_edges", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/queue/.state/variable_behavior", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
/* Then, get rid of the .state subdirectory itself (should be empty by now)
and everything matching <out_dir>/queue/id:*. */
fn = alloc_printf("%s/queue/.state", out_dir);
if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/queue", out_dir);
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
/* All right, let's do <out_dir>/crashes/id:* and <out_dir>/hangs/id:*. */
if (!in_place_resume) {
fn = alloc_printf("%s/crashes/README.txt", out_dir);
unlink(fn); /* Ignore errors */
ck_free(fn);
}
fn = alloc_printf("%s/crashes", out_dir);
/* Make backup of the crashes directory if it's not empty and if we're
doing in-place resume. */
if (in_place_resume && rmdir(fn)) {
time_t cur_t = time(0);
struct tm* t = localtime(&cur_t);
#ifndef SIMPLE_FILES
u8* nfn = alloc_printf("%s.%04u-%02u-%02u-%02u:%02u:%02u", fn,
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
#else
u8* nfn = alloc_printf("%s_%04u%02u%02u%02u%02u%02u", fn,
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
#endif /* ^!SIMPLE_FILES */
rename(fn, nfn); /* Ignore errors. */
ck_free(nfn);
}
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/hangs", out_dir);
/* Backup hangs, too. */
if (in_place_resume && rmdir(fn)) {
time_t cur_t = time(0);
struct tm* t = localtime(&cur_t);
#ifndef SIMPLE_FILES
u8* nfn = alloc_printf("%s.%04u-%02u-%02u-%02u:%02u:%02u", fn,
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
#else
u8* nfn = alloc_printf("%s_%04u%02u%02u%02u%02u%02u", fn,
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
#endif /* ^!SIMPLE_FILES */
rename(fn, nfn); /* Ignore errors. */
ck_free(nfn);
}
if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed;
ck_free(fn);
/* And now, for some finishing touches. */
fn = alloc_printf("%s/.cur_input", out_dir);
if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed;
ck_free(fn);
fn = alloc_printf("%s/fuzz_bitmap", out_dir);
if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed;
ck_free(fn);
if (!in_place_resume) {
fn = alloc_printf("%s/fuzzer_stats", out_dir);
if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed;
ck_free(fn);
}
fn = alloc_printf("%s/plot_data", out_dir);
if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed;
ck_free(fn);
OKF("Output dir cleanup successful.");
/* Wow... is that all? If yes, celebrate! */
return;
dir_cleanup_failed:
SAYF("\n" cLRD "[-] " cRST
"Whoops, the fuzzer tried to reuse your output directory, but bumped into\n"
" some files that shouldn't be there or that couldn't be removed - so it\n"
" decided to abort! This happened while processing this path:\n\n"
" %s\n\n"
" Please examine and manually delete the files, or specify a different\n"
" output location for the tool.\n", fn);
FATAL("Output directory cleanup failed");
}
static void check_term_size(void);
/* A spiffy retro stats screen! This is called every stats_update_freq
execve() calls, plus in several other circumstances. */
static void show_stats(void) {
static u64 last_stats_ms, last_plot_ms, last_ms, last_execs;
static double avg_exec;
double t_byte_ratio, stab_ratio;
u64 cur_ms;
u32 t_bytes, t_bits;
u32 banner_len, banner_pad;
u8 tmp[256];
cur_ms = get_cur_time();
/* If not enough time has passed since last UI update, bail out. */
if (cur_ms - last_ms < 1000 / UI_TARGET_HZ) return;
/* Check if we're past the 10 minute mark. */
if (cur_ms - start_time > 10 * 60 * 1000) run_over10m = 1;
/* Calculate smoothed exec speed stats. */
if (!last_execs) {
avg_exec = ((double)total_execs) * 1000 / (cur_ms - start_time);
} else {
double cur_avg = ((double)(total_execs - last_execs)) * 1000 /
(cur_ms - last_ms);
/* If there is a dramatic (5x+) jump in speed, reset the indicator
more quickly. */
if (cur_avg * 5 < avg_exec || cur_avg / 5 > avg_exec)
avg_exec = cur_avg;
avg_exec = avg_exec * (1.0 - 1.0 / AVG_SMOOTHING) +
cur_avg * (1.0 / AVG_SMOOTHING);
}
last_ms = cur_ms;
last_execs = total_execs;
/* Tell the callers when to contact us (as measured in execs). */
stats_update_freq = avg_exec / (UI_TARGET_HZ * 10);
if (!stats_update_freq) stats_update_freq = 1;
/* Do some bitmap stats. */
t_bytes = count_non_255_bytes(virgin_bits);
t_byte_ratio = ((double)t_bytes * 100) / MAP_SIZE;
if (t_bytes)
stab_ratio = 100 - ((double)var_byte_count) * 100 / t_bytes;
else
stab_ratio = 100;
/* Roughly every minute, update fuzzer stats and save auto tokens. */
if (cur_ms - last_stats_ms > STATS_UPDATE_SEC * 1000) {
last_stats_ms = cur_ms;
write_stats_file(t_byte_ratio, stab_ratio, avg_exec);
save_auto();
write_bitmap();
}
/* Every now and then, write plot data. */
if (cur_ms - last_plot_ms > PLOT_UPDATE_SEC * 1000) {
last_plot_ms = cur_ms;
maybe_update_plot_file(t_byte_ratio, avg_exec);
}
/* Honor AFL_EXIT_WHEN_DONE and AFL_BENCH_UNTIL_CRASH. */
if (!dumb_mode && cycles_wo_finds > 100 && !pending_not_fuzzed &&
getenv("AFL_EXIT_WHEN_DONE")) stop_soon = 2;
if (total_crashes && getenv("AFL_BENCH_UNTIL_CRASH")) stop_soon = 2;
/* If we're not on TTY, bail out. */
if (not_on_tty) return;
/* Compute some mildly useful bitmap stats. */
t_bits = (MAP_SIZE << 3) - count_bits(virgin_bits);
/* Now, for the visuals... */
if (clear_screen) {
SAYF(TERM_CLEAR CURSOR_HIDE);
clear_screen = 0;
check_term_size();
}
SAYF(TERM_HOME);
if (term_too_small) {
SAYF(cBRI "Your terminal is too small to display the UI.\n"
"Please resize terminal window to at least 80x25.\n" cRST);
return;
}
/* Let's start by drawing a centered banner. */
banner_len = (crash_mode ? 24 : 22) + strlen(VERSION) + strlen(use_banner);
banner_pad = (80 - banner_len) / 2;
memset(tmp, ' ', banner_pad);
sprintf(tmp + banner_pad, "%s " cLCY VERSION cLGN
" (%s)", crash_mode ? cPIN "peruvian were-rabbit" :
cYEL "american fuzzy lop", use_banner);
SAYF("\n%s\n\n", tmp);
/* "Handy" shortcuts for drawing boxes... */
#define bSTG bSTART cGRA
#define bH2 bH bH
#define bH5 bH2 bH2 bH
#define bH10 bH5 bH5
#define bH20 bH10 bH10
#define bH30 bH20 bH10
#define SP5 " "
#define SP10 SP5 SP5
#define SP20 SP10 SP10
/* Lord, forgive me this. */
SAYF(SET_G1 bSTG bLT bH bSTOP cCYA " process timing " bSTG bH30 bH5 bH2 bHB
bH bSTOP cCYA " overall results " bSTG bH5 bRT "\n");
if (dumb_mode) {
strcpy(tmp, cRST);
} else {
u64 min_wo_finds = (cur_ms - last_path_time) / 1000 / 60;
/* First queue cycle: don't stop now! */
if (queue_cycle == 1 || min_wo_finds < 15) strcpy(tmp, cMGN); else
/* Subsequent cycles, but we're still making finds. */
if (cycles_wo_finds < 25 || min_wo_finds < 30) strcpy(tmp, cYEL); else
/* No finds for a long time and no test cases to try. */
if (cycles_wo_finds > 100 && !pending_not_fuzzed && min_wo_finds > 120)
strcpy(tmp, cLGN);
/* Default: cautiously OK to stop? */
else strcpy(tmp, cLBL);
}
SAYF(bV bSTOP " run time : " cRST "%-34s " bSTG bV bSTOP
" cycles done : %s%-5s " bSTG bV "\n",
DTD(cur_ms, start_time), tmp, DI(queue_cycle - 1));
/* We want to warn people about not seeing new paths after a full cycle,
except when resuming fuzzing or running in non-instrumented mode. */
if (!dumb_mode && (last_path_time || resuming_fuzz || queue_cycle == 1 ||
in_bitmap || crash_mode)) {
SAYF(bV bSTOP " last new path : " cRST "%-34s ",
DTD(cur_ms, last_path_time));
} else {
if (dumb_mode)
SAYF(bV bSTOP " last new path : " cPIN "n/a" cRST
" (non-instrumented mode) ");
else
SAYF(bV bSTOP " last new path : " cRST "none yet " cLRD
"(odd, check syntax!) ");
}
SAYF(bSTG bV bSTOP " total paths : " cRST "%-5s " bSTG bV "\n",
DI(queued_paths));
/* Highlight crashes in red if found, denote going over the KEEP_UNIQUE_CRASH
limit with a '+' appended to the count. */
sprintf(tmp, "%s%s", DI(unique_crashes),
(unique_crashes >= KEEP_UNIQUE_CRASH) ? "+" : "");
SAYF(bV bSTOP " last uniq crash : " cRST "%-34s " bSTG bV bSTOP
" uniq crashes : %s%-6s " bSTG bV "\n",
DTD(cur_ms, last_crash_time), unique_crashes ? cLRD : cRST,
tmp);
sprintf(tmp, "%s%s", DI(unique_hangs),
(unique_hangs >= KEEP_UNIQUE_HANG) ? "+" : "");
SAYF(bV bSTOP " last uniq hang : " cRST "%-34s " bSTG bV bSTOP
" uniq hangs : " cRST "%-6s " bSTG bV "\n",
DTD(cur_ms, last_hang_time), tmp);
SAYF(bVR bH bSTOP cCYA " cycle progress " bSTG bH20 bHB bH bSTOP cCYA
" map coverage " bSTG bH bHT bH20 bH2 bH bVL "\n");
/* This gets funny because we want to print several variable-length variables
together, but then cram them into a fixed-width field - so we need to
put them in a temporary buffer first. */
sprintf(tmp, "%s%s (%0.02f%%)", DI(current_entry),
queue_cur->favored ? "" : "*",
((double)current_entry * 100) / queued_paths);
SAYF(bV bSTOP " now processing : " cRST "%-17s " bSTG bV bSTOP, tmp);
sprintf(tmp, "%0.02f%% / %0.02f%%", ((double)queue_cur->bitmap_size) *
100 / MAP_SIZE, t_byte_ratio);
SAYF(" map density : %s%-21s " bSTG bV "\n", t_byte_ratio > 70 ? cLRD :
((t_bytes < 200 && !dumb_mode) ? cPIN : cRST), tmp);
sprintf(tmp, "%s (%0.02f%%)", DI(cur_skipped_paths),
((double)cur_skipped_paths * 100) / queued_paths);
SAYF(bV bSTOP " paths timed out : " cRST "%-17s " bSTG bV, tmp);
sprintf(tmp, "%0.02f bits/tuple",
t_bytes ? (((double)t_bits) / t_bytes) : 0);
SAYF(bSTOP " count coverage : " cRST "%-21s " bSTG bV "\n", tmp);
SAYF(bVR bH bSTOP cCYA " stage progress " bSTG bH20 bX bH bSTOP cCYA
" findings in depth " bSTG bH20 bVL "\n");
sprintf(tmp, "%s (%0.02f%%)", DI(queued_favored),
((double)queued_favored) * 100 / queued_paths);
/* Yeah... it's still going on... halp? */
SAYF(bV bSTOP " now trying : " cRST "%-21s " bSTG bV bSTOP
" favored paths : " cRST "%-22s " bSTG bV "\n", stage_name, tmp);
if (!stage_max) {
sprintf(tmp, "%s/-", DI(stage_cur));
} else {
sprintf(tmp, "%s/%s (%0.02f%%)", DI(stage_cur), DI(stage_max),
((double)stage_cur) * 100 / stage_max);
}
SAYF(bV bSTOP " stage execs : " cRST "%-21s " bSTG bV bSTOP, tmp);
sprintf(tmp, "%s (%0.02f%%)", DI(queued_with_cov),
((double)queued_with_cov) * 100 / queued_paths);
SAYF(" new edges on : " cRST "%-22s " bSTG bV "\n", tmp);
sprintf(tmp, "%s (%s%s unique)", DI(total_crashes), DI(unique_crashes),
(unique_crashes >= KEEP_UNIQUE_CRASH) ? "+" : "");
if (crash_mode) {
SAYF(bV bSTOP " total execs : " cRST "%-21s " bSTG bV bSTOP
" new crashes : %s%-22s " bSTG bV "\n", DI(total_execs),
unique_crashes ? cLRD : cRST, tmp);
} else {
SAYF(bV bSTOP " total execs : " cRST "%-21s " bSTG bV bSTOP
" total crashes : %s%-22s " bSTG bV "\n", DI(total_execs),
unique_crashes ? cLRD : cRST, tmp);
}
/* Show a warning about slow execution. */
if (avg_exec < 100) {
sprintf(tmp, "%s/sec (%s)", DF(avg_exec), avg_exec < 20 ?
"zzzz..." : "slow!");
SAYF(bV bSTOP " exec speed : " cLRD "%-21s ", tmp);
} else {
sprintf(tmp, "%s/sec", DF(avg_exec));
SAYF(bV bSTOP " exec speed : " cRST "%-21s ", tmp);
}
sprintf(tmp, "%s (%s%s unique)", DI(total_tmouts), DI(unique_tmouts),
(unique_hangs >= KEEP_UNIQUE_HANG) ? "+" : "");
SAYF (bSTG bV bSTOP " total tmouts : " cRST "%-22s " bSTG bV "\n", tmp);
/* Aaaalmost there... hold on! */
SAYF(bVR bH cCYA bSTOP " fuzzing strategy yields " bSTG bH10 bH bHT bH10
bH5 bHB bH bSTOP cCYA " path geometry " bSTG bH5 bH2 bH bVL "\n");
if (skip_deterministic) {
strcpy(tmp, "n/a, n/a, n/a");
} else {
sprintf(tmp, "%s/%s, %s/%s, %s/%s",
DI(stage_finds[STAGE_FLIP1]), DI(stage_cycles[STAGE_FLIP1]),
DI(stage_finds[STAGE_FLIP2]), DI(stage_cycles[STAGE_FLIP2]),
DI(stage_finds[STAGE_FLIP4]), DI(stage_cycles[STAGE_FLIP4]));
}
SAYF(bV bSTOP " bit flips : " cRST "%-37s " bSTG bV bSTOP " levels : "
cRST "%-10s " bSTG bV "\n", tmp, DI(max_depth));
if (!skip_deterministic)
sprintf(tmp, "%s/%s, %s/%s, %s/%s",
DI(stage_finds[STAGE_FLIP8]), DI(stage_cycles[STAGE_FLIP8]),
DI(stage_finds[STAGE_FLIP16]), DI(stage_cycles[STAGE_FLIP16]),
DI(stage_finds[STAGE_FLIP32]), DI(stage_cycles[STAGE_FLIP32]));
SAYF(bV bSTOP " byte flips : " cRST "%-37s " bSTG bV bSTOP " pending : "
cRST "%-10s " bSTG bV "\n", tmp, DI(pending_not_fuzzed));
if (!skip_deterministic)
sprintf(tmp, "%s/%s, %s/%s, %s/%s",
DI(stage_finds[STAGE_ARITH8]), DI(stage_cycles[STAGE_ARITH8]),
DI(stage_finds[STAGE_ARITH16]), DI(stage_cycles[STAGE_ARITH16]),
DI(stage_finds[STAGE_ARITH32]), DI(stage_cycles[STAGE_ARITH32]));
SAYF(bV bSTOP " arithmetics : " cRST "%-37s " bSTG bV bSTOP " pend fav : "
cRST "%-10s " bSTG bV "\n", tmp, DI(pending_favored));
if (!skip_deterministic)
sprintf(tmp, "%s/%s, %s/%s, %s/%s",
DI(stage_finds[STAGE_INTEREST8]), DI(stage_cycles[STAGE_INTEREST8]),
DI(stage_finds[STAGE_INTEREST16]), DI(stage_cycles[STAGE_INTEREST16]),
DI(stage_finds[STAGE_INTEREST32]), DI(stage_cycles[STAGE_INTEREST32]));
SAYF(bV bSTOP " known ints : " cRST "%-37s " bSTG bV bSTOP " own finds : "
cRST "%-10s " bSTG bV "\n", tmp, DI(queued_discovered));
if (!skip_deterministic)
sprintf(tmp, "%s/%s, %s/%s, %s/%s",
DI(stage_finds[STAGE_EXTRAS_UO]), DI(stage_cycles[STAGE_EXTRAS_UO]),
DI(stage_finds[STAGE_EXTRAS_UI]), DI(stage_cycles[STAGE_EXTRAS_UI]),
DI(stage_finds[STAGE_EXTRAS_AO]), DI(stage_cycles[STAGE_EXTRAS_AO]));
SAYF(bV bSTOP " dictionary : " cRST "%-37s " bSTG bV bSTOP
" imported : " cRST "%-10s " bSTG bV "\n", tmp,
sync_id ? DI(queued_imported) : (u8*)"n/a");
sprintf(tmp, "%s/%s, %s/%s",
DI(stage_finds[STAGE_HAVOC]), DI(stage_cycles[STAGE_HAVOC]),
DI(stage_finds[STAGE_SPLICE]), DI(stage_cycles[STAGE_SPLICE]));
SAYF(bV bSTOP " havoc : " cRST "%-37s " bSTG bV bSTOP, tmp);
if (t_bytes) sprintf(tmp, "%0.02f%%", stab_ratio);
else strcpy(tmp, "n/a");
SAYF(" stability : %s%-10s " bSTG bV "\n", (stab_ratio < 85 && var_byte_count > 40)
? cLRD : ((queued_variable && (!persistent_mode || var_byte_count > 20))
? cMGN : cRST), tmp);
if (!bytes_trim_out) {
sprintf(tmp, "n/a, ");
} else {
sprintf(tmp, "%0.02f%%/%s, ",
((double)(bytes_trim_in - bytes_trim_out)) * 100 / bytes_trim_in,
DI(trim_execs));
}
if (!blocks_eff_total) {
u8 tmp2[128];
sprintf(tmp2, "n/a");
strcat(tmp, tmp2);
} else {
u8 tmp2[128];
sprintf(tmp2, "%0.02f%%",
((double)(blocks_eff_total - blocks_eff_select)) * 100 /
blocks_eff_total);
strcat(tmp, tmp2);
}
SAYF(bV bSTOP " trim : " cRST "%-37s " bSTG bVR bH20 bH2 bH2 bRB "\n"
bLB bH30 bH20 bH2 bH bRB bSTOP cRST RESET_G1, tmp);
/* Provide some CPU utilization stats. */
if (cpu_core_count) {
double cur_runnable = get_runnable_processes();
u32 cur_utilization = cur_runnable * 100 / cpu_core_count;
u8* cpu_color = cCYA;
/* If we could still run one or more processes, use green. */
if (cpu_core_count > 1 && cur_runnable + 1 <= cpu_core_count)
cpu_color = cLGN;
/* If we're clearly oversubscribed, use red. */
if (!no_cpu_meter_red && cur_utilization >= 150) cpu_color = cLRD;
#ifdef HAVE_AFFINITY
if (cpu_aff >= 0) {
SAYF(SP10 cGRA "[cpu%03u:%s%3u%%" cGRA "]\r" cRST,
MIN(cpu_aff, 999), cpu_color,
MIN(cur_utilization, 999));
} else {
SAYF(SP10 cGRA " [cpu:%s%3u%%" cGRA "]\r" cRST,
cpu_color, MIN(cur_utilization, 999));
}
#else
SAYF(SP10 cGRA " [cpu:%s%3u%%" cGRA "]\r" cRST,
cpu_color, MIN(cur_utilization, 999));
#endif /* ^HAVE_AFFINITY */
} else SAYF("\r");
/* Hallelujah! */
fflush(0);
}
/* 在处理输入目录后显示初始化统计信息 */
static void show_init_stats(void) {
struct queue_entry* q = queue; // 队列的当前元素
u32 min_bits = 0, max_bits = 0; // 最小和最大位图大小
u64 min_us = 0, max_us = 0; // 最小和最大执行时间(微秒)
u64 avg_us = 0; // 平均执行时间
u32 max_len = 0; // 最大测试用例长度
// 如果有校准周期,计算平均执行时间
if (total_cal_cycles) avg_us = total_cal_us / total_cal_cycles;
// 遍历队列,找到最小和最大位图大小、执行时间和测试用例长度
while (q) {
if (!min_us || q->exec_us < min_us) min_us = q->exec_us;
if (q->exec_us > max_us) max_us = q->exec_us;
if (!min_bits || q->bitmap_size < min_bits) min_bits = q->bitmap_size;
if (q->bitmap_size > max_bits) max_bits = q->bitmap_size;
if (q->len > max_len) max_len = q->len;
q = q->next; // 移动到下一个队列元素
}
SAYF("\n");
// 如果平均执行时间较长给出警告并调整havoc_div的值
if (avg_us > (qemu_mode ? 50000 : 10000))
WARNF(cLRD "The target binary is pretty slow! See %s/perf_tips.txt.",
doc_path);
// 如果平均执行时间超过特定阈值调整havoc_div的值
if (avg_us > 50000) havoc_div = 10; /* 0-19 execs/sec */
else if (avg_us > 20000) havoc_div = 5; /* 20-49 execs/sec */
else if (avg_us > 10000) havoc_div = 2; /* 50-100 execs/sec */
// 如果不是从会话中恢复,给出一些警告
if (!resuming_fuzz) {
if (max_len > 50 * 1024)
WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!",
DMS(max_len), doc_path);
else if (max_len > 10 * 1024)
WARNF("Some test cases are big (%s) - see %s/perf_tips.txt.",
DMS(max_len), doc_path);
if (useless_at_start && !in_bitmap)
WARNF(cLRD "Some test cases look useless. Consider using a smaller set.");
if (queued_paths > 100)
WARNF(cLRD "You probably have far too many input files! Consider trimming down.");
else if (queued_paths > 20)
WARNF("You have lots of input files; try starting small.");
}
// 显示统计信息
OKF("Here are some useful stats:\n\n"
cGRA " Test case count : " cRST "%u favored, %u variable, %u total\n"
cGRA " Bitmap range : " cRST "%u to %u bits (average: %0.02f bits)\n"
cGRA " Exec timing : " cRST "%s to %s us (average: %s us)\n",
queued_favored, queued_variable, queued_paths, min_bits, max_bits,
((double)total_bitmap_size) / (total_bitmap_entries ? total_bitmap_entries : 1),
DI(min_us), DI(max_us), DI(avg_us));
// 如果没有指定超时时间,计算一个合适的超时时间
if (!timeout_given) {
/* Figure out the appropriate timeout. The basic idea is: 5x average or
1x max, rounded up to EXEC_TM_ROUND ms and capped at 1 second. */
if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000;
else if (avg_us > 10000) exec_tmout = avg_us * 3 / 1000;
else exec_tmout = avg_us * 5 / 1000;
exec_tmout = MAX(exec_tmout, max_us / 1000);
exec_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND;
if (exec_tmout > EXEC_TIMEOUT) exec_tmout = EXEC_TIMEOUT;
ACTF("No -t option specified, so I'll use exec timeout of %u ms.",
exec_tmout);
timeout_given = 1;
} else if (timeout_given == 3) {
ACTF("Applying timeout settings from resumed session (%u ms).", exec_tmout);
}
/* In dumb mode, re-running every timing out test case with a generous time
limit is very expensive, so let's select a more conservative default. */
if (dumb_mode && !getenv("AFL_HANG_TMOUT"))
hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100);
OKF("All set and ready to roll!");
}
/* 找到大于或等于val的最小的2的幂次方假设val小于2^31 */
static u32 next_p2(u32 val) {
u32 ret = 1;
while (val > ret) ret <<= 1;
return ret;
}
// 对输入案例进行修剪,尝试移除部分输入数据,看是否会影响程序的执行路径。
static u8 trim_case(char** argv, struct queue_entry* q, u8* in_buf) {
static u8 tmp[64]; // 临时缓冲区
static u8 clean_trace[MAP_SIZE]; // 用于存储清理后的执行路径
u8 needs_write = 0, fault = 0; // 标记是否需要写入和是否出现故障
u32 trim_exec = 0; // 修剪执行次数
u32 remove_len; // 要移除的长度
u32 len_p2; // 长度的下一个2的幂次
// 如果输入长度小于5直接返回0不进行修剪
if (q->len < 5) return 0;
stage_name = tmp; // 设置当前阶段名称
bytes_trim_in += q->len; // 更新修剪输入的总字节数
// 选择初始块长度,从大步长开始
len_p2 = next_p2(q->len); // 获取q->len的下一个2的幂次
remove_len = MAX(len_p2 / TRIM_START_STEPS, TRIM_MIN_BYTES); // 计算移除长度
// 继续修剪,直到步数过高或步长过小
while (remove_len >= MAX(len_p2 / TRIM_END_STEPS, TRIM_MIN_BYTES)) {
u32 remove_pos = remove_len; // 移除位置
// 格式化修剪信息
sprintf(tmp, "trim %s/%s", DI(remove_len), DI(remove_len));
stage_cur = 0; // 当前阶段
stage_max = q->len / remove_len; // 最大阶段数
while (remove_pos < q->len) { // 循环移除每个位置的数据
u32 trim_avail = MIN(remove_len, q->len - remove_pos); // 可修剪的可用长度
u32 cksum; // 校验和
// 写入带有间隔的输入数据
write_with_gap(in_buf, q->len, remove_pos, trim_avail);
fault = run_target(argv, exec_tmout); // 运行目标程序
trim_execs++; // 更新修剪执行次数
// 如果出现错误或停止请求,跳转到修剪中止
if (stop_soon || fault == FAULT_ERROR) goto abort_trimming;
// 计算执行路径的校验和
cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
// 如果删除数据没有影响执行路径,将其永久移除
if (cksum == q->exec_cksum) {
u32 move_tail = q->len - remove_pos - trim_avail; // 需要移动的尾部长度
q->len -= trim_avail; // 更新长度
len_p2 = next_p2(q->len); // 更新下一个2的幂次
// 移动数据
memmove(in_buf + remove_pos, in_buf + remove_pos + trim_avail, move_tail);
// 如果需要,保存一个干净的执行路径
if (!needs_write) {
needs_write = 1;
memcpy(clean_trace, trace_bits, MAP_SIZE); // 复制执行路径
}
} else {
remove_pos += remove_len; // 否则,移动到下一个位置
}
// 定期更新屏幕显示
if (!(trim_exec++ % stats_update_freq)) show_stats();
stage_cur++; // 更新当前阶段
}
remove_len >>= 1; // 减少移除长度
}
// 如果对in_buf进行了修改需要更新磁盘上的测试用例
if (needs_write) {
s32 fd; // 文件描述符
// 删除旧的测试用例文件
unlink(q->fname); // 忽略错误
// 创建新的测试用例文件
fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", q->fname); // 错误处理
// 写入新的测试用例数据
ck_write(fd, in_buf, q->len, q->fname);
close(fd); // 关闭文件描述符
// 更新执行路径和分数
memcpy(trace_bits, clean_trace, MAP_SIZE);
update_bitmap_score(q);
}
abort_trimming: // 修剪中止标签
bytes_trim_out += q->len; // 更新修剪输出的总字节数
return fault; // 返回故障状态
}
// 写入修改后的测试用例运行程序处理结果。处理错误条件如果需要中止则返回1。
// 这是fuzz_one()的辅助函数。
EXP_ST u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) {
u8 fault; // 故障状态
// 如果存在后处理函数
if (post_handler) {
// 调用后处理函数
out_buf = post_handler(out_buf, &len);
if (!out_buf || !len) return 0; // 如果返回为空或长度为0则返回0
}
// 写入测试用例
write_to_testcase(out_buf, len);
fault = run_target(argv, exec_tmout); // 运行目标程序
// 如果需要停止则返回1
if (stop_soon) return 1;
// 如果出现超时故障
if (fault == FAULT_TMOUT) {
// 如果连续超时次数超过限制,则跳过当前路径
if (subseq_tmouts++ > TMOUT_LIMIT) {
cur_skipped_paths++;
return 1;
}
} else {
subseq_tmouts = 0; // 重置连续超时次数
}
// 用户可以通过SIGUSR1信号请求放弃当前输入
if (skip_requested) {
skip_requested = 0; // 重置跳过请求
cur_skipped_paths++; // 增加跳过路径数
return 1; // 返回1
}
// 处理故障,更新发现的队列
queued_discovered += save_if_interesting(argv, out_buf, len, fault);
// 定期更新统计信息
if (!(stage_cur % stats_update_freq) || stage_cur + 1 == stage_max)
show_stats();
return 0; // 返回0表示继续执行
}
/* 辅助函数,用于在模糊测试中的块操作选择随机块长度。
只要max_len大于0就不会返回零 */
static u32 choose_block_len(u32 limit) {
u32 min_value, max_value;
u32 rlim = MIN(queue_cycle, 3); // 限制随机数生成的范围
if (!run_over10m) rlim = 1; // 如果没有超过10M的运行限制则设置rlim为1
// 根据随机数选择不同的块长度范围
switch (UR(rlim)) {
case 0:
min_value = 1; // 最小长度设置为1
max_value = HAVOC_BLK_SMALL; // 最大长度设置为小值
break;
case 1:
min_value = HAVOC_BLK_SMALL; // 最小长度设置为小值
max_value = HAVOC_BLK_MEDIUM; // 最大长度设置为中等值
break;
default:
if (UR(10)) {
min_value = HAVOC_BLK_MEDIUM; // 随机选择中等或大长度
max_value = HAVOC_BLK_LARGE;
} else {
min_value = HAVOC_BLK_LARGE; // 随机选择大或超大长度
max_value = HAVOC_BLK_XL;
}
}
// 如果最小值大于限制则设置最小值为1
if (min_value >= limit) min_value = 1;
// 返回一个在指定范围内的随机块长度
return min_value + UR(MIN(max_value, limit) - min_value + 1);
}
/* 计算案例的期望分数以调整havoc模糊测试的长度。
这是一个辅助函数用于fuzz_one()。也许这些常数中的一些应该
进入config.h文件中 */
static u32 calculate_score(struct queue_entry* q) {
u32 avg_exec_us = total_cal_us / total_cal_cycles; // 计算平均执行时间
u32 avg_bitmap_size = total_bitmap_size / total_bitmap_entries; // 计算平均位图大小
u32 perf_score = 100; // 初始化性能分数
/* 根据此路径的执行速度与全局平均值相比,调整分数。
乘数范围从0.1x到3x。快速输入的成本较低因此给予更多的运行时间。 */
if (q->exec_us * 0.1 > avg_exec_us) perf_score = 10;
else if (q->exec_us * 0.25 > avg_exec_us) perf_score = 25;
else if (q->exec_us * 0.5 > avg_exec_us) perf_score = 50;
else if (q->exec_us * 0.75 > avg_exec_us) perf_score = 75;
else if (q->exec_us * 4 < avg_exec_us) perf_score = 300;
else if (q->exec_us * 3 < avg_exec_us) perf_score = 200;
else if (q->exec_us * 2 < avg_exec_us) perf_score = 150;
/* 根据位图大小调整分数。工作理论是更好的覆盖率转化为更好的目标。
乘数从0.25x到3x。 */
if (q->bitmap_size * 0.3 > avg_bitmap_size) perf_score *= 3;
else if (q->bitmap_size * 0.5 > avg_bitmap_size) perf_score *= 2;
else if (q->bitmap_size * 0.75 > avg_bitmap_size) perf_score *= 1.5;
else if (q->bitmap_size * 3 < avg_bitmap_size) perf_score *= 0.25;
else if (q->bitmap_size * 2 < avg_bitmap_size) perf_score *= 0.5;
else if (q->bitmap_size * 1.5 < avg_bitmap_size) perf_score *= 0.75;
/* 根据handicap调整分数。Handicap与我们了解此路径的时间成正比。
后来者允许运行更长时间,直到它们赶上其他路径。 */
if (q->handicap >= 4) {
perf_score *= 4; // 如果handicap大于等于4则乘以4
q->handicap -= 4; // 减少handicap值
} else if (q->handicap) {
perf_score *= 2; // 如果handicap大于0则乘以2
q->handicap--; // 减少handicap值
}
/* 根据输入深度进行最终调整,假设模糊测试更深层的测试用例
更有可能发现传统模糊测试无法发现的问题。 */
switch (q->depth) {
case 0 ... 3: break; // 如果深度在0到3之间不调整分数
case 4 ... 7: perf_score *= 2; break; // 如果深度在4到7之间乘以2
case 8 ... 13: perf_score *= 3; break; // 如果深度在8到13之间乘以3
case 14 ... 25: perf_score *= 4; break; // 如果深度在14到25之间乘以4
default: perf_score *= 5; // 默认情况下乘以5
}
/* 确保我们不超过限制。 */
if (perf_score > HAVOC_MAX_MULT * 100) perf_score = HAVOC_MAX_MULT * 100; // 如果分数超过最大限制,则设置为最大限制
return perf_score; // 返回计算出的分数
}
/* Helper function to see if a particular change (xor_val = old ^ new) could
be a product of deterministic bit flips with the lengths and stepovers
attempted by afl-fuzz. This is used to avoid dupes in some of the
deterministic fuzzing operations that follow bit flips. We also
return 1 if xor_val is zero, which implies that the old and attempted new
values are identical and the exec would be a waste of time. */
// 检测一个值是否可能是通过位翻转操作得到的。
static u8 could_be_bitflip(u32 xor_val) {
u32 sh = 0; // 用于记录位移的变量
// 如果xor_val为0表示没有位被翻转返回1。
if (!xor_val) return 1;
// 将xor_val左移直到最低位为1记录移动的位数。
while (!(xor_val & 1)) { sh++; xor_val >>= 1; }
// 如果xor_val是1、3或15表示只有1、2或4位被翻转返回1。
if (xor_val == 1 || xor_val == 3 || xor_val == 15) return 1;
// 如果sh不是8的倍数那么8位、16位或32位的模式不可能通过位移得到返回0。
if (sh & 7) return 0;
// 如果xor_val是0xff、0xffff或0xffffffff表示8位、16位或32位的所有位都被翻转返回1。
if (xor_val == 0xff || xor_val == 0xffff || xor_val == 0xffffffff)
return 1;
// 如果以上条件都不满足返回0。
return 0;
}
// 检测一个值是否可能是通过算术操作从另一个值得到的。
static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) {
u32 i, ov = 0, nv = 0, diffs = 0; // 用于循环和比较的变量
// 如果old_val和new_val相同返回1。
if (old_val == new_val) return 1;
// 检查每个字节是否有差异并尝试通过单字节的调整来生成new_val。
for (i = 0; i < blen; i++) {
u8 a = old_val >> (8 * i), // 获取old_val的第i个字节
b = new_val >> (8 * i); // 获取new_val的第i个字节
if (a != b) { diffs++; ov = a; nv = b; } // 如果字节不同,增加差异计数
}
// 如果只有一个字节不同并且差异在可接受的范围内返回1。
if (diffs == 1) {
if ((u8)(ov - nv) <= ARITH_MAX ||
(u8)(nv - ov) <= ARITH_MAX) return 1;
}
// 如果blen为1表示只有单字节返回0。
if (blen == 1) return 0;
// 检查每两个字节是否有差异并尝试通过双字节的调整来生成new_val。
diffs = 0;
for (i = 0; i < blen / 2; i++) {
u16 a = old_val >> (16 * i), // 获取old_val的第i个双字节
b = new_val >> (16 * i); // 获取new_val的第i个双字节
if (a != b) { diffs++; ov = a; nv = b; } // 如果双字节不同,增加差异计数
}
// 如果只有一个双字节不同并且差异在可接受的范围内返回1。
if (diffs == 1) {
if ((u16)(ov - nv) <= ARITH_MAX ||
(u16)(nv - ov) <= ARITH_MAX) return 1;
// 尝试交换字节顺序后再次检查
ov = SWAP16(ov); nv = SWAP16(nv);
if ((u16)(ov - nv) <= ARITH_MAX ||
(u16)(nv - ov) <= ARITH_MAX) return 1;
}
// 如果blen为4表示有四个字节检查整个四字节的差异。
if (blen == 4) {
if ((u32)(old_val - new_val) <= ARITH_MAX ||
(u32)(new_val - old_val) <= ARITH_MAX) return 1;
// 尝试交换字节顺序后再次检查
new_val = SWAP32(new_val);
old_val = SWAP32(old_val);
if ((u32)(old_val - new_val) <= ARITH_MAX ||
(u32)(new_val - old_val) <= ARITH_MAX) return 1;
}
// 如果以上条件都不满足返回0。
return 0;
}
// 检测一个值是否可能是通过插入特定的整数得到的,考虑到之前已经插入过的值。
static u8 could_be_interest(u32 old_val, u32 new_val, u8 blen, u8 check_le) {
u32 i, j;
// 如果old_val和new_val相同返回1。
if (old_val == new_val) return 1;
// 检查是否可以通过在old_val中插入一个字节的值来得到new_val。
for (i = 0; i < blen; i++) {
for (j = 0; j < sizeof(interesting_8); j++) {
u32 tval = (old_val & ~(0xff << (i * 8))) | // 清除old_val的第i个字节
(((u8)interesting_8[j]) << (i * 8)); // 插入新的字节值
if (new_val == tval) return 1; // 如果得到的值与new_val相同返回1
}
}
// 如果blen为2并且check_le为0表示不需要检查小端字节序返回0。
if (blen == 2 && !check_le) return 0;
// 检查是否可以通过在old_val中插入一个双字节的值来得到new_val。
for (i = 0; i < blen - 1; i++) {
for (j = 0; j < sizeof(interesting_16) / 2; j++) {
u32 tval = (old_val & ~(0xffff << (i * 8))) | // 清除old_val的第i个双字节
(((u16)interesting_16[j]) << (i * 8)); // 插入新的双字节值
if (new_val == tval) return 1; // 如果得到的值与new_val相同返回1
// 如果blen大于2尝试交换字节顺序后再次检查
if (blen > 2) {
tval = (old_val & ~(0xffff << (i * 8))) |
(SWAP16(interesting_16[j]) << (i * 8));
if (new_val == tval) return 1; // 如果得到的值与new_val相同返回1
}
}
}
// 如果blen为4并且check_le为1表示需要检查大端字节序。
if (blen == 4 && check_le) {
// 检查是否可以通过插入一个四字节的值来得到new_val只考虑小端
for (j = 0; j < sizeof(interesting_32) / 4; j++)
if (new_val == (u32)interesting_32[j]) return 1;
}
// 如果以上条件都不满足返回0。
return 0;
}
/* Take the current entry from the queue, fuzz it for a while. This
function is a tad too long... returns 0 if fuzzed successfully, 1 if
skipped or bailed out. */
static u8 fuzz_one(char** argv) {
// 定义局部变量,用于存储输入数据长度、文件描述符、临时数据长度等。
s32 len, fd, temp_len, i, j;
// 分配指针,用于存储输入缓冲区、输出缓冲区、原始输入数据等。
u8 *in_buf, *out_buf, *orig_in, *ex_tmp, *eff_map = 0;
// 定义变量,用于记录混沌队列中的路径数量、原始命中次数和新的命中次数。
u64 havoc_queued, orig_hit_cnt, new_hit_cnt;
// 定义变量,用于记录拼接周期、性能得分、原始性能得分、先前校验和、效应器计数。
u32 splice_cycle = 0, perf_score = 100, orig_perf, prev_cksum, eff_cnt = 1;
// 定义返回值、确定性测试标志。
u8 ret_val = 1, doing_det = 0;
// 定义一个数组,用于存储自动收集的额外数据,以及一个计数器。
u8 a_collect[MAX_AUTO_EXTRA];
u32 a_len = 0;
#ifdef IGNORE_FINDS
// 如果定义了IGNORE_FINDS宏跳过不在初始数据集中的条目。
if (queue_cur->depth > 1) return 1;
#else
// 如果有待处理的优选输入且不在IGNORE_FINDS模式。
if (pending_favored) {
// 如果队列中有新的优选输入,可能跳过已模糊测试的或非优选的情况。
if ((queue_cur->was_fuzzed || !queue_cur->favored) &&
UR(100) < SKIP_TO_NEW_PROB) return 1;
} else if (!dumb_mode && !queue_cur->favored && queued_paths > 10) {
// 否则,对于非优选情况,也可能跳过,但概率较低。
// 对于已经模糊测试的输入,跳过的概率更高;对于从未模糊测试的输入,概率更低。
if (queue_cycle > 1 && !queue_cur->was_fuzzed) {
// 如果是队列中的第二轮且当前输入未被模糊测试过,根据概率跳过。
if (UR(100) < SKIP_NFAV_NEW_PROB) return 1;
} else {
// 对于已经模糊测试过的输入,根据概率跳过。
if (UR(100) < SKIP_NFAV_OLD_PROB) return 1;
}
}
#endif /* ^IGNORE_FINDS */
// 如果当前环境不是终端tty则打印模糊测试的进度信息。
if (not_on_tty) {
ACTF("Fuzzing test case #%u (%u total, %llu uniq crashes found)...",
current_entry, queued_paths, unique_crashes);
fflush(stdout);
}
// 打开当前测试用例文件并将其映射到内存中。
fd = open(queue_cur->fname, O_RDONLY);
// 如果打开文件失败,则打印错误信息并终止程序。
if (fd < 0) PFATAL("Unable to open '%s'", queue_cur->fname);
// 获取文件长度。
len = queue_cur->len;
// 将文件内容映射到内存中的in_buf变量。
orig_in = in_buf = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
// 如果映射失败,则打印错误信息并终止程序。
if (orig_in == MAP_FAILED) PFATAL("Unable to mmap '%s'", queue_cur->fname);
// 关闭文件描述符。
close(fd);
// 分配内存用于输出缓冲区这里使用ck_alloc_nozero来分配非零内存。
out_buf = ck_alloc_nozero(len);
// 初始化连续超时计数器。
subseq_tmouts = 0;
// 设置当前深度。
cur_depth = queue_cur->depth;
/*******************************************
* CALIBRATION (only if failed earlier on) *
*******************************************/
// 如果当前测试用例之前校准失败,则尝试重新校准。
if (queue_cur->cal_failed) {
u8 res = FAULT_TMOUT;
// 如果校准失败次数小于允许的最大次数,则尝试重新校准。
if (queue_cur->cal_failed < CAL_CHANCES) {
// 重置exec_cksum以便重新执行测试用例。
queue_cur->exec_cksum = 0;
// 执行校准函数。
res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0);
// 如果校准失败,则终止程序。
if (res == FAULT_ERROR)
FATAL("Unable to execute target application");
}
// 如果需要停止或者校准结果不是预期的崩溃模式,则跳过当前测试用例。
if (stop_soon || res != crash_mode) {
cur_skipped_paths++;
goto abandon_entry;
}
}
/************
* TRIMMING *
************/
// 如果没有启用dumb_mode并且当前测试用例尚未修剪则执行修剪操作。
if (!dumb_mode && !queue_cur->trim_done) {
u8 res = trim_case(argv, queue_cur, in_buf);
// 如果修剪失败,则终止程序。
if (res == FAULT_ERROR)
FATAL("Unable to execute target application");
// 如果需要停止,则跳过当前测试用例。
if (stop_soon) {
cur_skipped_paths++;
goto abandon_entry;
}
// 标记为已修剪,不再尝试修剪。
queue_cur->trim_done = 1;
// 更新文件长度。
if (len != queue_cur->len) len = queue_cur->len;
}
// 将输入缓冲区的内容复制到输出缓冲区。
memcpy(out_buf, in_buf, len);
/*********************
* PERFORMANCE SCORE *
*********************/
// 计算性能得分。
orig_perf = perf_score = calculate_score(queue_cur);
// 如果启用了-d选项或者已经对当前测试用例执行过确定性模糊测试或者它已经通过了早期的确定性测试则跳过确定性测试阶段。
if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det)
goto havoc_stage;
// 如果执行路径校验和将当前测试用例排除在当前主实例的范围之外,则跳过确定性测试。
if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1)
goto havoc_stage;
// 标记为正在执行确定性测试。
doing_det = 1;
/*********************************************
* SIMPLE BITFLIP (+dictionary construction) *
********************************************/
// 定义一个宏,用于翻转位。
#define FLIP_BIT(_ar, _b) do { \
u8* _arf = (u8*)(_ar); \
u32 _bf = (_b); \
_arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \
} while (0)
// 执行简单的位翻转测试。
stage_short = "flip1";
stage_max = len << 3;
stage_name = "bitflip 1/1";
stage_val_type = STAGE_VAL_NONE;
// 初始化原始命中计数器。
orig_hit_cnt = queued_paths + unique_crashes;
// 保存之前的校验和。
prev_cksum = queue_cur->exec_cksum;
// 遍历每个位并执行位翻转测试。
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
// 计算当前位所在的字节。
stage_cur_byte = stage_cur >> 3;
// 翻转当前位。
FLIP_BIT(out_buf, stage_cur);
// 执行模糊测试。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
// 恢复原始位状态。
FLIP_BIT(out_buf, stage_cur);
// 在翻转最低有效位时,执行额外的检测以识别可能的语法标记。
if (!dumb_mode && (stage_cur & 7) == 7) {
// 计算新的校验和。
u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
// 如果到达文件末尾并且我们仍在收集字符串,则获取最终字符并强制输出。
if (stage_cur == stage_max - 1 && cksum == prev_cksum) {
if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3];
a_len++;
if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA)
maybe_add_auto(a_collect, a_len);
} else if (cksum != prev_cksum) {
// 否则,如果校验和已更改,则查看是否有值得收集的内容,并在是的情况下收集。
if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA)
maybe_add_auto(a_collect, a_len);
a_len = 0;
prev_cksum = cksum;
}
// 继续收集字符串,但只有在位翻转实际上产生了影响时,我们才不希望无操作标记。
if (cksum != queue_cur->exec_cksum) {
if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3];
a_len++;
}
}
}
// 初始化新的命中计数,这是待处理路径数和唯一崩溃数的总和。
new_hit_cnt = queued_paths + unique_crashes;
// 更新STAGE_FLIP1阶段的发现计数和周期数。
stage_finds[STAGE_FLIP1] += new_hit_cnt - orig_hit_cnt; // 发现计数增加新的命中数减去原始的命中数。
stage_cycles[STAGE_FLIP1] += stage_max; // 增加STAGE_FLIP1的最大周期数。
/* 两个行走的位。 */
// 设置当前阶段的名称和简称。
stage_name = "bitflip 2/1";
stage_short = "flip2";
// 计算STAGE_FLIP1阶段的最大值即输入数据长度的8倍减1。
stage_max = (len << 3) - 1;
// 将新的命中计数赋值给原始命中计数,以便下一次迭代使用。
orig_hit_cnt = new_hit_cnt;
// 循环遍历STAGE_FLIP1阶段的最大值。
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
// 计算当前位操作的字节索引。
stage_cur_byte = stage_cur >> 3;
// 翻转输出缓冲区中当前位置和下一个位置的位。
FLIP_BIT(out_buf, stage_cur);
FLIP_BIT(out_buf, stage_cur + 1);
// 如果common_fuzz_stuff函数返回失败跳转到abandon_entry标签处。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
// 恢复输出缓冲区中当前位置和下一个位置的位到原始状态。
FLIP_BIT(out_buf, stage_cur);
FLIP_BIT(out_buf, stage_cur + 1);
}
// 计算新的命中次数,这是当前队列中的路径数加上唯一的崩溃次数。
new_hit_cnt = queued_paths + unique_crashes;
// 更新FLIP2阶段的发现次数和周期数。
stage_finds[STAGE_FLIP2] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_FLIP2] += stage_max;
// 定义FLIP4阶段的名称和简称。
stage_name = "bitflip 4/1";
stage_short = "flip4";
// 计算FLIP4阶段的最大值这是输入长度的三倍减去3。
stage_max = (len << 3) - 3;
// 将新的命中次数保存为原始命中次数,以便在下一个阶段使用。
orig_hit_cnt = new_hit_cnt;
// 循环,对每个可能的位位置进行操作。
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
// 计算当前操作的字节位置。
stage_cur_byte = stage_cur >> 3;
// 翻转当前位置及其后三个位置的位。
FLIP_BIT(out_buf, stage_cur);
FLIP_BIT(out_buf, stage_cur + 1);
FLIP_BIT(out_buf, stage_cur + 2);
FLIP_BIT(out_buf, stage_cur + 3);
// 如果common_fuzz_stuff函数返回真则跳转到abandon_entry标签。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
// 恢复原始位状态。
FLIP_BIT(out_buf, stage_cur);
FLIP_BIT(out_buf, stage_cur + 1);
FLIP_BIT(out_buf, stage_cur + 2);
FLIP_BIT(out_buf, stage_cur + 3);
}
// 更新FLIP4阶段的发现次数和周期数。
new_hit_cnt = queued_paths + unique_crashes;
stage_finds[STAGE_FLIP4] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_FLIP4] += stage_max;
// 定义一些宏用于计算effector map中的位置和长度。
#define EFF_APOS(_p) ((_p) >> EFF_MAP_SCALE2)
#define EFF_REM(_x) ((_x) & ((1 << EFF_MAP_SCALE2) - 1))
#define EFF_ALEN(_l) (EFF_APOS(_l) + !!EFF_REM(_l))
#define EFF_SPAN_ALEN(_p, _l) (EFF_APOS((_p) + (_l) - 1) - EFF_APOS(_p) + 1)
// 初始化下一个步骤的effector map并标记第一个和最后一个字节为活跃的。
eff_map = ck_alloc(EFF_ALEN(len));
eff_map[0] = 1;
// 如果最后一个字节的位置不等于0则标记它为活跃的并增加eff_cnt计数器。
if (EFF_APOS(len - 1) != 0) {
eff_map[EFF_APOS(len - 1)] = 1;
eff_cnt++;
}
// 定义FLIP8阶段的名称和简称。
stage_name = "bitflip 8/8";
stage_short = "flip8";
// 设置FLIP8阶段的最大值为输入长度。
stage_max = len;
// 将新的命中次数保存为原始命中次数,以便在下一个阶段使用。
orig_hit_cnt = new_hit_cnt;
// 循环,对每个字节进行操作。
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
// 计算当前操作的字节位置。
stage_cur_byte = stage_cur;
// 翻转当前字节的所有位。
out_buf[stage_cur] ^= 0xFF;
// 如果common_fuzz_stuff函数返回真则跳转到abandon_entry标签。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
// 如果当前字节在effector map中未被标记则进行进一步的检查。
if (!eff_map[EFF_APOS(stage_cur)]) {
u32 cksum;
// 如果处于dumb模式或文件非常短则不进行checksum计算。
if (!dumb_mode && len >= EFF_MIN_LEN)
cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
else
cksum = ~queue_cur->exec_cksum;
// 如果checksum与预期不符则标记该字节为活跃的并增加eff_cnt计数器。
if (cksum != queue_cur->exec_cksum) {
eff_map[EFF_APOS(stage_cur)] = 1;
eff_cnt++;
}
}
// 恢复原始字节状态。
out_buf[stage_cur] ^= 0xFF;
}
/* 如果效应器effector映射的密度超过EFF_MAX_PERC指定的百分比
则标记整个映射为值得fuzzing模糊测试因为我们不会节省太多时间。 */
if (eff_cnt != EFF_ALEN(len) &&
eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) {
// 如果超过阈值则将整个效应器映射标记为1所有位都值得测试
memset(eff_map, 1, EFF_ALEN(len));
// 更新被选中进行模糊测试的块的数量
blocks_eff_select += EFF_ALEN(len);
} else {
// 如果没有超过阈值,则只增加实际有效应器的计数
blocks_eff_select += eff_cnt;
}
// 更新总共被考虑进行模糊测试的块的数量
blocks_eff_total += EFF_ALEN(len);
// 更新在当前阶段发现的新问题(如路径或崩溃)的数量
new_hit_cnt = queued_paths + unique_crashes;
// 更新当前阶段STAGE_FLIP8的发现和周期计数
stage_finds[STAGE_FLIP8] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_FLIP8] += stage_max;
/* 处理两个行走的字节。 */
// 如果长度小于2则跳过位翻转
if (len < 2) goto skip_bitflip;
// 设置当前阶段的名称和简称
stage_name = "bitflip 16/8";
stage_short = "flip16";
stage_cur = 0; // 当前阶段的当前进度
stage_max = len - 1; // 当前阶段的最大进度
// 记录原始的命中计数
orig_hit_cnt = new_hit_cnt;
// 遍历输入数据,每次处理两个字节
for (i = 0; i < len - 1; i++) {
// 检查效应器映射,如果当前和下一个字节都不是效应器,则跳过
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {
stage_max--;
continue;
}
// 设置当前处理的字节位置
stage_cur_byte = i;
// 翻转当前两个字节的所有位
*(u16*)(out_buf + i) ^= 0xFFFF;
// 执行模糊测试的常见操作如果失败则跳转到abandon_entry
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
// 恢复原始数据
*(u16*)(out_buf + i) ^= 0xFFFF;
}
// 更新新发现的问题数量和周期计数
new_hit_cnt = queued_paths + unique_crashes;
stage_finds[STAGE_FLIP16] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_FLIP16] += stage_max;
// 如果长度小于4则跳过四字节位翻转
if (len < 4) goto skip_bitflip;
/* 处理四个行走的字节。 */
// 设置当前阶段的名称和简称
stage_name = "bitflip 32/8";
stage_short = "flip32";
stage_cur = 0;
stage_max = len - 3;
// 记录原始的命中计数
orig_hit_cnt = new_hit_cnt;
// 遍历输入数据,每次处理四个字节
for (i = 0; i < len - 3; i++) {
// 检查效应器映射,如果当前和接下来三个字节都不是效应器,则跳过
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] &&
!eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) {
stage_max--;
continue;
}
// 设置当前处理的字节位置
stage_cur_byte = i;
// 翻转当前四个字节的所有位
*(u32*)(out_buf + i) ^= 0xFFFFFFFF;
// 执行模糊测试的常见操作如果失败则跳转到abandon_entry
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
// 恢复原始数据
*(u32*)(out_buf + i) ^= 0xFFFFFFFF;
}
// 更新新发现的问题数量和周期计数
new_hit_cnt = queued_paths + unique_crashes;
stage_finds[STAGE_FLIP32] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_FLIP32] += stage_max;
// 跳过算术操作的标签
skip_bitflip:
// 如果设置了no_arith标志则跳过算术操作
if (no_arith) goto skip_arith;
/**************************
* ARITHMETIC INC/DEC *
*************************/
// 8位算术操作
stage_name = "arith 8/8";
stage_short = "arith8";
stage_cur = 0; // 当前阶段的当前值
stage_max = 2 * len * ARITH_MAX; // 最大可能的值
stage_val_type = STAGE_VAL_LE; // 值的类型,这里设置为小端
orig_hit_cnt = new_hit_cnt; // 原始的命中次数
// 对输入缓冲区中的每个字节进行操作
for (i = 0; i < len; i++) {
u8 orig = out_buf[i]; // 原始字节值
// 如果当前位置不在影响映射中,则跳过
if (!eff_map[EFF_APOS(i)]) {
stage_max -= 2 * ARITH_MAX;
continue;
}
stage_cur_byte = i; // 当前处理的字节位置
// 对每个可能的增量进行操作
for (j = 1; j <= ARITH_MAX; j++) {
u8 r = orig ^ (orig + j); // 计算增加j后的值
// 如果结果不可能是位翻转的结果,则进行算术操作
if (!could_be_bitflip(r)) {
stage_cur_val = j;
out_buf[i] = orig + j; // 应用增加操作
// 如果公共模糊测试函数返回成功则跳转到abandon_entry标签
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
r = orig ^ (orig - j); // 计算减少j后的值
// 如果结果不可能是位翻转的结果,则进行算术操作
if (!could_be_bitflip(r)) {
stage_cur_val = -j;
out_buf[i] = orig - j; // 应用减少操作
// 如果公共模糊测试函数返回成功则跳转到abandon_entry标签
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
out_buf[i] = orig; // 恢复原始值
}
}
// 更新命中次数和阶段统计信息
new_hit_cnt = queued_paths + unique_crashes;
stage_finds[STAGE_ARITH8] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_ARITH8] += stage_max;
// 16位算术操作包括小端和大端
if (len < 2) goto skip_arith;
stage_name = "arith 16/8";
stage_short = "arith16";
stage_cur = 0;
stage_max = 4 * (len - 1) * ARITH_MAX;
orig_hit_cnt = new_hit_cnt;
// 对输入缓冲区中每两个字节进行操作
for (i = 0; i < len - 1; i++) {
u16 orig = *(u16*)(out_buf + i); // 原始的16位值
// 如果当前位置和下一个位置都不在影响映射中,则跳过
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {
stage_max -= 4 * ARITH_MAX;
continue;
}
stage_cur_byte = i;
// 对每个可能的增量进行操作
for (j = 1; j <= ARITH_MAX; j++) {
u16 r1 = orig ^ (orig + j), // 小端增加
r2 = orig ^ (orig - j), // 小端减少
r3 = orig ^ SWAP16(SWAP16(orig) + j), // 大端增加
r4 = orig ^ SWAP16(SWAP16(orig) - j); // 大端减少
// 尝试小端增加和减少操作
stage_val_type = STAGE_VAL_LE;
if ((orig & 0xff) + j > 0xff && !could_be_bitflip(r1)) {
stage_cur_val = j;
*(u16*)(out_buf + i) = orig + j;
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
if ((orig & 0xff) < j && !could_be_bitflip(r2)) {
stage_cur_val = -j;
*(u16*)(out_buf + i) = orig - j;
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
// 尝试大端增加和减少操作
stage_val_type = STAGE_VAL_BE;
if ((orig >> 8) + j > 0xff && !could_be_bitflip(r3)) {
stage_cur_val = j;
*(u16*)(out_buf + i) = SWAP16(SWAP16(orig) + j);
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
if ((orig >> 8) < j && !could_be_bitflip(r4)) {
stage_cur_val = -j;
*(u16*)(out_buf + i) = SWAP16(SWAP16(orig) - j);
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
*(u16*)(out_buf + i) = orig; // 恢复原始值
}
}
// 更新命中次数和阶段统计信息
new_hit_cnt = queued_paths + unique_crashes;
stage_finds[STAGE_ARITH16] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_ARITH16] += stage_max;
// 32位算术操作包括小端和大端
if (len < 4) goto skip_arith;
stage_name = "arith 32/8";
stage_short = "arith32";
stage_cur = 0;
stage_max = 4 * (len - 3) * ARITH_MAX;
orig_hit_cnt = new_hit_cnt;
// 对输入缓冲区中每四个字节进行操作
for (i = 0; i < len - 3; i++) {
u32 orig = *(u32*)(out_buf + i); // 原始的32位值
// 如果当前位置和接下来的三个位置都不在影响映射中,则跳过
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] &&
!eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) {
stage_max -= 4 * ARITH_MAX;
continue;
}
stage_cur_byte = i;
// 对每个可能的增量进行操作
for (j = 1; j <= ARITH_MAX; j++) {
u32 r1 = orig ^ (orig + j), // 小端增加
r2 = orig ^ (orig - j), // 小端减少
r3 = orig ^ SWAP32(SWAP32(orig) + j), // 大端增加
r4 = orig ^ SWAP32(SWAP32(orig) - j); // 大端减少
// 尝试小端增加和减少操作
stage_val_type = STAGE_VAL_LE;
if ((orig & 0xffff) + j > 0xffff && !could_be_bitflip(r1)) {
stage_cur_val = j;
*(u32*)(out_buf + i) = orig + j;
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
if ((orig & 0xffff) < j && !could_be_bitflip(r2)) {
stage_cur_val = -j;
*(u32*)(out_buf + i) = orig - j;
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
// 尝试大端增加和减少操作
stage_val_type = STAGE_VAL_BE;
if ((SWAP32(orig) & 0xffff) + j > 0xffff && !could_be_bitflip(r3)) {
stage_cur_val = j;
*(u32*)(out_buf + i) = SWAP32(SWAP32(orig) + j);
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
// 如果原始值的大端格式的低16位小于增量j并且结果不可能是位翻转的结果
if ((SWAP32(orig) & 0xffff) < j && !could_be_bitflip(r4)) {
// 设置当前阶段的值为负的增量j
stage_cur_val = -j;
// 对当前的32位值进行大端格式的减法操作并更新输出缓冲区
*(u32*)(out_buf + i) = SWAP32(SWAP32(orig) - j);
// 执行公共的模糊测试操作如果测试中断则跳转到abandon_entry标签
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
// 如果测试没有中断,增加当前阶段的计数器
stage_cur++;
} else {
// 如果操作不可能产生新的命中,减少最大阶段计数
stage_max--;
}
// 恢复原始的32位值以便下一次迭代使用
*(u32*)(out_buf + i) = orig;
}
}
new_hit_cnt = queued_paths + unique_crashes;
stage_finds[STAGE_ARITH32] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_ARITH32] += stage_max;
// 如果设置了跳过算术处理的标志,则跳到下一个阶段。
skip_arith:
/**********************
* INTERESTING VALUES *
**********************/
// 设置当前阶段的名称和简称。
stage_name = "interest 8/8";
stage_short = "int8";
stage_cur = 0; // 初始化当前阶段的计数器。
stage_max = len * sizeof(interesting_8); // 最大次数为输入数据长度与有趣8位整数数组大小的乘积。
stage_val_type = STAGE_VAL_LE; // 设置阶段值类型为小端。
orig_hit_cnt = new_hit_cnt; // 记录原始的命中次数。
// 设置8位整数。
for (i = 0; i < len; i++) {
u8 orig = out_buf[i]; // 保存原始字节。
// 如果当前位置在效应器映射中没有设置,则跳过。
if (!eff_map[EFF_APOS(i)]) {
stage_max -= sizeof(interesting_8);
continue;
}
stage_cur_byte = i; // 设置当前处理的字节位置。
// 遍历有趣的8位整数数组。
for (j = 0; j < sizeof(interesting_8); j++) {
// 如果该值可能是位翻转或算术操作的结果,则跳过。
if (could_be_bitflip(orig ^ (u8)interesting_8[j]) ||
could_be_arith(orig, (u8)interesting_8[j], 1)) {
stage_max--;
continue;
}
stage_cur_val = interesting_8[j]; // 设置当前阶段的值。
out_buf[i] = interesting_8[j]; // 设置有趣的值。
// 执行模糊测试操作,如果需要则放弃当前输入。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
out_buf[i] = orig; // 恢复原始值。
stage_cur++; // 增加当前阶段的计数器。
}
}
// 更新新的命中次数。
new_hit_cnt = queued_paths + unique_crashes;
// 记录在当前阶段发现的新问题数量和周期计数。
stage_finds[STAGE_INTEREST8] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_INTEREST8] += stage_max;
// 如果设置了不进行算术处理或输入数据长度小于2则跳过16位整数的处理。
if (no_arith || len < 2) goto skip_interest;
// 设置当前阶段的名称和简称。
stage_name = "interest 16/8";
stage_short = "int16";
stage_cur = 0; // 初始化当前阶段的计数器。
stage_max = 2 * (len - 1) * (sizeof(interesting_16) >> 1); // 最大次数为输入数据长度减1与有趣16位整数数组大小的乘积。
orig_hit_cnt = new_hit_cnt; // 记录原始的命中次数。
// 设置16位整数包括小端和大端。
for (i = 0; i < len - 1; i++) {
u16 orig = *(u16*)(out_buf + i); // 保存原始的16位整数。
// 如果当前位置和下一位在效应器映射中没有设置,则跳过。
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {
stage_max -= sizeof(interesting_16);
continue;
}
stage_cur_byte = i; // 设置当前处理的字节位置。
// 遍历有趣的16位整数数组。
for (j = 0; j < sizeof(interesting_16) / 2; j++) {
stage_cur_val = interesting_16[j]; // 设置当前阶段的值。
// 如果该值不可能是位翻转、算术操作或单字节有趣值插入的结果,则进行处理。
if (!could_be_bitflip(orig ^ (u16)interesting_16[j]) &&
!could_be_arith(orig, (u16)interesting_16[j], 2) &&
!could_be_interest(orig, (u16)interesting_16[j], 2, 0)) {
stage_val_type = STAGE_VAL_LE; // 设置阶段值类型为小端。
*(u16*)(out_buf + i) = interesting_16[j]; // 设置有趣的值。
// 执行模糊测试操作,如果需要则放弃当前输入。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++; // 增加当前阶段的计数器。
} else {
stage_max--; // 减少最大次数。
}
// 对于大端情况也进行相同的处理。
if ((u16)interesting_16[j] != SWAP16(interesting_16[j]) &&
!could_be_bitflip(orig ^ SWAP16(interesting_16[j])) &&
!could_be_arith(orig, SWAP16(interesting_16[j]), 2) &&
!could_be_interest(orig, SWAP16(interesting_16[j]), 2, 1)) {
stage_val_type = STAGE_VAL_BE; // 设置阶段值类型为大端。
*(u16*)(out_buf + i) = SWAP16(interesting_16[j]); // 设置有趣的值。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else {
stage_max--;
}
}
*(u16*)(out_buf + i) = orig; // 恢复原始值。
}
// 更新新的命中次数。
new_hit_cnt = queued_paths + unique_crashes;
// 记录在当前阶段发现的新问题数量和周期计数。
stage_finds[STAGE_INTEREST16] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_INTEREST16] += stage_max;
// 如果输入数据长度小于4则跳过32位整数的处理。
if (len < 4) goto skip_interest;
// 设置当前阶段的名称和简称。
stage_name = "interest 32/8";
stage_short = "int32";
stage_cur = 0; // 初始化当前阶段的计数器。
stage_max = 2 * (len - 3) * (sizeof(interesting_32) >> 2); // 最大次数为输入数据长度减3与有趣32位整数数组大小的乘积。
orig_hit_cnt = new_hit_cnt; // 记录原始的命中次数。
// 设置32位整数包括小端和大端。
for (i = 0; i < len - 3; i++) {
u32 orig = *(u32*)(out_buf + i); // 保存原始的32位整数。
// 如果当前位置和接下来三位在效应器映射中没有设置,则跳过。
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] &&
!eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) {
stage_max -= sizeof(interesting_32) >> 1;
continue;
}
stage_cur_byte = i; // 设置当前处理的字节位置。
// 遍历有趣的32位整数数组。
for (j = 0; j < sizeof(interesting_32) / 4; j++) {
stage_cur_val = interesting_32[j]; // 设置当前阶段的值。
// 如果该值不可能是位翻转、算术操作或词有趣值插入的结果,则进行处理。
if (!could_be_bitflip(orig ^ (u32)interesting_32[j]) &&
!could_be_arith(orig, interesting_32[j], 4) &&
!could_be_interest(orig, interesting_32[j], 4, 0)) {
stage_val_type = STAGE_VAL_LE; // 设置阶段值类型为小端。
*(u32*)(out_buf + i) = interesting_32[j]; // 设置有趣的值。
// 执行模糊测试操作,如果需要则放弃当前输入。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++; // 增加当前阶段的计数器。
} else stage_max--; // 减少最大次数。
// 对于大端情况也进行相同的处理。
if ((u32)interesting_32[j] != SWAP32(interesting_32[j]) &&
!could_be_bitflip(orig ^ SWAP32(interesting_32[j])) &&
!could_be_arith(orig, SWAP32(interesting_32[j]), 4) &&
!could_be_interest(orig, SWAP32(interesting_32[j]), 4, 1)) {
// 设置阶段值类型为大端。
stage_val_type = STAGE_VAL_BE;
*(u32*)(out_buf + i) = SWAP32(interesting_32[j]);
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
stage_cur++;
} else stage_max--;
}
// 恢复原始的32位整数值。
*(u32*)(out_buf + i) = orig;
// 完成对当前输入数据的所有有趣值设置后,更新新的命中次数。
new_hit_cnt = queued_paths + unique_crashes;
// 记录在当前阶段发现的新问题数量和周期计数。
stage_finds[STAGE_INTEREST32] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_INTEREST32] += stage_max;
// 如果没有用户定义的额外数据,则跳过此段代码。
skip_interest:
/********************
* DICTIONARY STUFF *
********************/
if (!extras_cnt) goto skip_user_extras;
// 使用用户提供的额外数据进行覆盖操作。
stage_name = "user extras (over)";
stage_short = "ext_UO";
stage_cur = 0; // 初始化当前阶段的计数器。
stage_max = extras_cnt * len; // 最大次数为额外数据的数量乘以输入数据的长度。
stage_val_type = STAGE_VAL_NONE; // 设置阶段值类型为无。
orig_hit_cnt = new_hit_cnt; // 记录原始的命中次数。
// 对输入数据的每个字节位置进行操作。
for (i = 0; i < len; i++) {
u32 last_len = 0;
stage_cur_byte = i; // 设置当前处理的字节位置。
// 遍历每个额外数据。
for (j = 0; j < extras_cnt; j++) {
// 如果额外数据的数量大于最大确定性额外数据,或者没有足够的空间插入数据,
// 或者该数据已经存在于输出缓冲区中,或者有效性映射显示该位置不适合插入,则跳过此额外数据。
if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) ||
extras[j].len > len - i ||
!memcmp(extras[j].data, out_buf + i, extras[j].len) ||
!memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len))) {
stage_max--;
continue;
}
last_len = extras[j].len;
memcpy(out_buf + i, extras[j].data, last_len); // 将额外数据复制到输出缓冲区的当前位置。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 对修改后的输出缓冲区执行通用模糊测试操作。
stage_cur++; // 增加当前阶段的计数器。
}
// 恢复所有被覆盖的内存。
memcpy(out_buf + i, in_buf + i, last_len);
}
// 更新新的命中次数。
new_hit_cnt = queued_paths + unique_crashes;
// 记录在当前阶段发现的新问题数量和周期计数。
stage_finds[STAGE_EXTRAS_UO] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_EXTRAS_UO] += stage_max;
// 用户提供的额外数据进行插入操作。
stage_name = "user extras (insert)";
stage_short = "ext_UI";
stage_cur = 0; // 初始化当前阶段的计数器。
stage_max = extras_cnt * (len + 1); // 最大次数为额外数据的数量乘以(输入数据的长度 + 1
orig_hit_cnt = new_hit_cnt; // 记录原始的命中次数。
// 分配临时缓冲区,大小为输入数据的长度加上最大字典文件的大小。
ex_tmp = ck_alloc(len + MAX_DICT_FILE);
// 遍历输入数据的每个位置以及末尾。
for (i = 0; i <= len; i++) {
stage_cur_byte = i; // 设置当前处理的字节位置。
// 遍历每个额外数据。
for (j = 0; j < extras_cnt; j++) {
if (len + extras[j].len > MAX_FILE) {
stage_max--;
continue;
}
// 插入额外数据。
memcpy(ex_tmp + i, extras[j].data, extras[j].len);
// 复制原始数据的剩余部分。
memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i);
if (common_fuzz_stuff(argv, ex_tmp, len + extras[j].len)) {
ck_free(ex_tmp);
goto abandon_entry;
}
stage_cur++; // 增加当前阶段的计数器。
}
// 复制原始数据的当前部分。
ex_tmp[i] = out_buf[i];
}
// 释放临时缓冲区。
ck_free(ex_tmp);
// 更新新的命中次数。
new_hit_cnt = queued_paths + unique_crashes;
// 记录在当前阶段发现的新问题数量和周期计数。
stage_finds[STAGE_EXTRAS_UI] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_EXTRAS_UI] += stage_max;
// 如果没有用户提供的额外数据,则跳过处理。
skip_user_extras:
if (!a_extras_cnt) goto skip_extras;
// 设置当前阶段的名称和简短名称。
stage_name = "auto extras (over)";
stage_short = "ext_AO";
// 初始化当前阶段的计数器和最大值,最大值是额外数据的数量乘以输入数据的长度。
stage_cur = 0;
stage_max = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len;
// 设置阶段值类型为无。
stage_val_type = STAGE_VAL_NONE;
// 记录原始的命中次数。
orig_hit_cnt = new_hit_cnt;
// 遍历输入数据的每个字节位置。
for (i = 0; i < len; i++) {
u32 last_len = 0;
// 设置当前处理的字节位置。
stage_cur_byte = i;
// 遍历每个额外数据。
for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) {
// 如果额外数据的大小超过了剩余长度,或者该数据已经存在于输出缓冲区中,
// 或者有效性映射显示该位置不适合插入,则跳过此额外数据。
if (a_extras[j].len > len - i ||
!memcmp(a_extras[j].data, out_buf + i, a_extras[j].len) ||
!memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, a_extras[j].len))) {
stage_max--;
continue;
}
// 记录额外数据的长度,并将其复制到输出缓冲区的当前位置。
last_len = a_extras[j].len;
memcpy(out_buf + i, a_extras[j].data, last_len);
// 对修改后的输出缓冲区执行通用模糊测试操作,如果需要则放弃当前输入。
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
// 增加当前阶段的计数器。
stage_cur++;
}
// 恢复所有被覆盖的内存。
memcpy(out_buf + i, in_buf + i, last_len);
}
// 更新新的命中次数。
new_hit_cnt = queued_paths + unique_crashes;
// 记录在当前阶段发现的新问题数量和周期计数。
stage_finds[STAGE_EXTRAS_AO] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_EXTRAS_AO] += stage_max;
// 跳过额外数据的处理。
skip_extras:
// 如果我们到达这里而没有跳转到havoc_stage或abandon_entry
// 则我们已经完成了确定性的步骤,并可以在.state目录中标记为完成。
if (!queue_cur->passed_det) mark_as_det_done(queue_cur);
//****************
//* RANDOM HAVOC *
//****************
// 到达随机混沌havoc阶段。
havoc_stage:
stage_cur_byte = -1;
// 如果当前是拼接周期,则生成不同的描述。
if (!splice_cycle) {
// 设置混沌阶段的名称和简短名称。
stage_name = "havoc";
stage_short = "havoc";
// 计算混沌阶段的最大运行次数,基于性能得分和一些常数。
stage_max = (doing_det ? HAVOC_CYCLES_INIT : HAVOC_CYCLES) *
perf_score / havoc_div / 100;
} else {
// 如果是拼接周期,设置特定的名称和最大运行次数。
static u8 tmp[32];
perf_score = orig_perf;
sprintf(tmp, "splice %u", splice_cycle);
stage_name = tmp;
stage_short = "splice";
stage_max = SPLICE_HAVOC * perf_score / havoc_div / 100;
}
// 确保最大运行次数不低于最小值。
if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN;
// 设置临时长度为输入数据的长度。
temp_len = len;
// 记录原始的命中次数。
orig_hit_cnt = queued_paths + unique_crashes;
// 记录当前队列中的路径数量。
havoc_queued = queued_paths;
/*
我们基本上执行几千次运行取决于perf_score在这些运行中我们获取输入文件并进行随机的叠加修改。
*/
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
// 随机选择一个叠加因子范围从1到2^(HAVOC_STACK_POW2)。
u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2));
// 将当前阶段的值设置为所选的叠加因子。
stage_cur_val = use_stacking;
// 根据叠加因子重复多次修改操作。
for (i = 0; i < use_stacking; i++) {
// 随机选择一个操作,如果存在额外的数据,则选项会更多。
switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) {
/*
在某个位置翻转单个位。这是一项基本的位操作,可以用来测试程序对位错误的敏感性。
*/
case 0:
FLIP_BIT(out_buf, UR(temp_len << 3));
break;
/*
将某个字节设置为一个有趣的值。这些值通常是那些在程序逻辑中具有特殊意义的值比如0, 1, -1等。
*/
case 1:
out_buf[UR(temp_len)] = interesting_8[UR(sizeof(interesting_8))];
break;
/*
将某个字word设置为一个有趣的值并随机选择字节序。
字节序的选择可以帮助测试程序对不同字节序的处理能力。
*/
case 2:
if (temp_len < 2) break;
if (UR(2)) {
*(u16*)(out_buf + UR(temp_len - 1)) = interesting_16[UR(sizeof(interesting_16) >> 1)];
} else {
*(u16*)(out_buf + UR(temp_len - 1)) = SWAP16(interesting_16[UR(sizeof(interesting_16) >> 1)]);
}
break;
/*
将某个双字dword设置为一个有趣的值并随机选择字节序。
类似于字操作,但是针对更大的数据单元。
*/
case 3:
if (temp_len < 4) break;
if (UR(2)) {
*(u32*)(out_buf + UR(temp_len - 3)) = interesting_32[UR(sizeof(interesting_32) >> 2)];
} else {
*(u32*)(out_buf + UR(temp_len - 3)) = SWAP32(interesting_32[UR(sizeof(interesting_32) >> 2)]);
}
break;
/*
从某个字节随机减去一个值。这是一种简单的算术操作,可以用来测试程序的健壮性。
*/
case 4:
out_buf[UR(temp_len)] -= 1 + UR(ARITH_MAX);
break;
/*
向某个字节随机添加一个值。
*/
case 5:
out_buf[UR(temp_len)] += 1 + UR(ARITH_MAX);
break;
/*
从一个词word随机减去一个值并随机选择字节序。
*/
case 6:
if (temp_len < 2) break;
if (UR(2)) {
u32 pos = UR(temp_len - 1);
*(u16*)(out_buf + pos) -= 1 + UR(ARITH_MAX);
} else {
u32 pos = UR(temp_len - 1);
u16 num = 1 + UR(ARITH_MAX);
*(u16*)(out_buf + pos) = SWAP16(SWAP16(*(u16*)(out_buf + pos)) - num);
}
break;
/*
向一个词word随机添加一个值并随机选择字节序。
*/
case 7:
if (temp_len < 2) break;
if (UR(2)) {
u32 pos = UR(temp_len - 1);
*(u16*)(out_buf + pos) += 1 + UR(ARITH_MAX);
} else {
u32 pos = UR(temp_len - 1);
u16 num = 1 + UR(ARITH_MAX);
*(u16*)(out_buf + pos) = SWAP16(SWAP16(*(u16*)(out_buf + pos)) + num);
}
break;
/*
从一个双字dword随机减去一个值并随机选择字节序。
*/
case 8:
if (temp_len < 4) break;
if (UR(2)) {
u32 pos = UR(temp_len - 3);
*(u32*)(out_buf + pos) -= 1 + UR(ARITH_MAX);
} else {
u32 pos = UR(temp_len - 3);
u32 num = 1 + UR(ARITH_MAX);
*(u32*)(out_buf + pos) = SWAP32(SWAP32(*(u32*)(out_buf + pos)) - num);
}
break;
// case 9: 开始处理第9种情况即随机增加一个双字节dword可以是随机字节序。
case 9:
/* 如果临时数据长度小于4字节则无法进行操作因此跳出。 */
if (temp_len < 4) break;
// 以50%的概率选择是增加一个双字节还是进行字节序交换后增加。
if (UR(2)) {
u32 pos = UR(temp_len - 3);
// 直接在指定位置增加一个随机值范围从1到ARITH_MAX。
*(u32*)(out_buf + pos) += 1 + UR(ARITH_MAX);
} else {
u32 pos = UR(temp_len - 3);
u32 num = 1 + UR(ARITH_MAX);
// 先进行字节序交换,然后增加一个随机值,最后再进行字节序交换。
*(u32*)(out_buf + pos) =
SWAP32(SWAP32(*(u32*)(out_buf + pos)) + num);
}
break;
// case 10: 开始处理第10种情况即随机设置一个字节为随机值。
case 10:
/* 随机选择一个字节然后使用XOR操作将其设置为1到255之间的随机值以避免无操作no-op。 */
out_buf[UR(temp_len)] ^= 1 + UR(255);
break;
// case 11和case 12: 开始处理删除字节的操作。
case 11 ... 12: {
/* 删除字节。我们使得这个操作比插入(下一个选项)更有可能,以希望保持文件的合理大小。 */
u32 del_from, del_len;
// 如果临时数据长度小于2字节则无法进行删除操作因此跳出。
if (temp_len < 2) break;
/* 不要删除太多数据。 */
del_len = choose_block_len(temp_len - 1);
del_from = UR(temp_len - del_len + 1);
// 将删除位置之后的数据向前移动。
memmove(out_buf + del_from, out_buf + del_from + del_len,
temp_len - del_from - del_len);
temp_len -= del_len; // 更新临时数据长度。
break;
}
// case 13: 开始处理插入或克隆字节的操作。
case 13:
if (temp_len + HAVOC_BLK_XL < MAX_FILE) {
/* 克隆字节75%概率或插入一个常数块25%概率)。 */
u8 actually_clone = UR(4);
u32 clone_from, clone_to, clone_len;
u8* new_buf;
if (actually_clone) {
clone_len = choose_block_len(temp_len);
clone_from = UR(temp_len - clone_len + 1);
} else {
clone_len = choose_block_len(HAVOC_BLK_XL);
clone_from = 0;
}
clone_to = UR(temp_len);
new_buf = ck_alloc_nozero(temp_len + clone_len);
/* 头部 */
memcpy(new_buf, out_buf, clone_to);
/* 插入部分 */
if (actually_clone)
memcpy(new_buf + clone_to, out_buf + clone_from, clone_len);
else
memset(new_buf + clone_to,
UR(2) ? UR(256) : out_buf[UR(temp_len)], clone_len);
/* 尾部 */
memcpy(new_buf + clone_to + clone_len, out_buf + clone_to,
temp_len - clone_to);
ck_free(out_buf);
out_buf = new_buf;
temp_len += clone_len; // 更新临时数据长度。
}
break;
// case 14: 开始处理用随机选择的数据块或固定数据覆盖字节的操作。
case 14: {
u32 copy_from, copy_to, copy_len;
if (temp_len < 2) break;
copy_len = choose_block_len(temp_len - 1);
copy_from = UR(temp_len - copy_len + 1);
copy_to = UR(temp_len - copy_len + 1);
if (UR(4)) {
if (copy_from != copy_to)
memmove(out_buf + copy_to, out_buf + copy_from, copy_len);
} else {
memset(out_buf + copy_to,
UR(2) ? UR(256) : out_buf[UR(temp_len)], copy_len);
}
break;
}
/* 只有当字典中有额外数据时才能选择值15和16。 */
// case 15: 开始处理用额外数据覆盖字节的操作。
case 15: {
if (!extras_cnt || (a_extras_cnt && UR(2))) {
/* 没有用户指定的额外数据,或者随机数倾向于使用自动检测到的数据。 */
u32 use_extra = UR(a_extras_cnt);
u32 extra_len = a_extras[use_extra].len;
u32 insert_at;
if (extra_len > temp_len) break;
insert_at = UR(temp_len - extra_len + 1);
memcpy(out_buf + insert_at, a_extras[use_extra].data, extra_len);
} else {
/* 没有自动检测到的额外数据,或者随机数倾向于使用字典中的数据。 */
u32 use_extra = UR(extras_cnt);
u32 extra_len = extras[use_extra].len;
u32 insert_at;
if (extra_len > temp_len) break;
insert_at = UR(temp_len - extra_len + 1);
memcpy(out_buf + insert_at, extras[use_extra].data, extra_len);
}
break;
}
// case 16: 开始处理插入额外数据的操作。
case 16: {
u32 use_extra, extra_len, insert_at = UR(temp_len + 1);
u8* new_buf;
if (!extras_cnt || (a_extras_cnt && UR(2))) {
use_extra = UR(a_extras_cnt);
extra_len = a_extras[use_extra].len;
if (temp_len + extra_len >= MAX_FILE) break;
new_buf = ck_alloc_nozero(temp_len + extra_len);
/* 头部 */
memcpy(new_buf, out_buf, insert_at);
/* 插入部分 */
memcpy(new_buf + insert_at, a_extras[use_extra].data, extra_len);
} else {
use_extra = UR(extras_cnt);
extra_len = extras[use_extra].len;
if (temp_len + extra_len >= MAX_FILE) break;
new_buf = ck_alloc_nozero(temp_len + extra_len);
/* 头部 */
memcpy(new_buf, out_buf, insert_at);
/* 插入部分 */
memcpy(new_buf + insert_at, extras[use_extra].data, extra_len);
}
/* 尾部 */
memcpy(new_buf + insert_at + extra_len, out_buf + insert_at,
temp_len - insert_at);
ck_free(out_buf);
out_buf = new_buf;
temp_len += extra_len; // 更新临时数据长度。
break;
}
}
}
}
}
// 检查通用模糊测试函数是否指示我们应该放弃当前的输入。
// 如果common_fuzz_stuff函数返回真非零值则跳转到标签abandon_entry。
if (common_fuzz_stuff(argv, out_buf, temp_len))
goto abandon_entry;
// 如果out_buf在之前的处理中被破坏了我们需要将其恢复到原始大小和形状。
// 如果临时长度temp_len小于原始长度len我们需要重新分配out_buf的大小为len。
if (temp_len < len)
out_buf = ck_realloc(out_buf, len);
// 将temp_len设置回原始长度len。
temp_len = len;
// 将原始输入数据in_buf复制回out_buf以恢复其原始内容。
memcpy(out_buf, in_buf, len);
// 如果我们发现了新的问题或崩溃,我们应该在限制范围内继续运行更长时间。
if (queued_paths != havoc_queued) {
// 如果性能得分perf_score小于或等于最大乘数HAVOC_MAX_MULT乘以100
// 我们将当前阶段的最大尝试次数stage_max翻倍并将性能得分perf_score翻倍。
if (perf_score <= HAVOC_MAX_MULT * 100) {
stage_max *= 2;
perf_score *= 2;
}
// 更新havoc_queued为当前的queued_paths值。
havoc_queued = queued_paths;
}
// 计算新的发现数量包括新加入队列的路径数queued_paths和独特的崩溃数unique_crashes。
new_hit_cnt = queued_paths + unique_crashes;
// 如果当前不是拼接周期splice_cycle则更新HAVOC阶段的发现和周期计数。
if (!splice_cycle) {
stage_finds[STAGE_HAVOC] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_HAVOC] += stage_max;
} else {
// 如果当前是拼接周期则更新SPLICE阶段的发现和周期计数。
stage_finds[STAGE_SPLICE] += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_SPLICE] += stage_max;
}
// 如果没有定义IGNORE_FINDS宏则执行以下代码。
#ifndef IGNORE_FINDS
/************
* SPLICING *
************/
/* 这是一种最后的手段策略,当一轮完整的测试没有发现任何问题时触发。
它获取当前的输入文件,随机选择另一个输入,并在某个偏移量处将它们拼接在一起,
然后依赖havoc代码来变异这个新拼接的数据块。*/
retry_splicing:
// 如果启用了拼接并且拼接周期小于最大拼接周期数并且队列中有多个测试用例且当前测试用例长度大于1则尝试拼接操作。
if (use_splicing && splice_cycle++ < SPLICE_CYCLES &&
queued_paths > 1 && queue_cur->len > 1) {
struct queue_entry* target; // 指向目标队列条目的指针
u32 tid, split_at; // 目标ID和分割点
u8* new_buf; // 新的缓冲区
s32 f_diff, l_diff; // 第一个和最后一个不同字节的位置
/* 首先如果我们对in_buf进行了havoc操作的修改我们需要清理它... */
// 如果in_buf不是原始输入缓冲区释放它并将in_buf重置为原始输入缓冲区
if (in_buf != orig_in) {
ck_free(in_buf);
in_buf = orig_in;
len = queue_cur->len;
}
/* 随机选择一个队列条目并定位到它。不要与自己拼接。 */
// 随机选择一个目标ID确保它不是当前条目
do { tid = UR(queued_paths); } while (tid == current_entry);
splicing_with = tid; // 记录当前拼接的目标ID
target = queue; // 初始化目标指向队列头部
// 定位到目标队列条目
while (tid >= 100) { target = target->next_100; tid -= 100; }
while (tid--) target = target->next;
/* 确保目标有合理的长度。 */
// 确保目标条目长度足够,并且不是当前条目
while (target && (target->len < 2 || target == queue_cur)) {
target = target->next;
splicing_with++;
}
// 如果没有合适的目标,重试拼接
if (!target) goto retry_splicing;
/* 将测试用例读入新缓冲区。 */
// 打开目标文件
fd = open(target->fname, O_RDONLY);
// 如果打开失败,输出错误信息并退出
if (fd < 0) PFATAL("Unable to open '%s'", target->fname);
// 分配新缓冲区
new_buf = ck_alloc_nozero(target->len);
// 读取目标文件内容到新缓冲区
ck_read(fd, new_buf, target->len, target->fname);
// 关闭文件描述符
close(fd);
/* 寻找合适的拼接位置,在第一个和最后一个不同字节之间。如果差异只是单个字节或很少几个字节,则放弃。 */
// 定位两个缓冲区中的差异
locate_diffs(in_buf, new_buf, MIN(len, target->len), &f_diff, &l_diff);
// 如果没有合适的差异或者差异太小,释放新缓冲区并重试拼接
if (f_diff < 0 || l_diff < 2 || f_diff == l_diff) {
ck_free(new_buf);
goto retry_splicing;
}
/* 在第一个和最后一个不同字节之间选择一个位置进行分割。 */
// 选择分割点
split_at = f_diff + UR(l_diff - f_diff);
/* 执行拼接操作。 */
// 更新长度为目标长度
len = target->len;
// 将分割点之前的数据复制到新缓冲区
memcpy(new_buf, in_buf, split_at);
// 更新输入缓冲区为新缓冲区
in_buf = new_buf;
// 释放旧的输出缓冲区
ck_free(out_buf);
// 分配新的输出缓冲区
out_buf = ck_alloc_nozero(len);
// 将新缓冲区内容复制到输出缓冲区
memcpy(out_buf, in_buf, len);
// 跳转到havoc阶段
goto havoc_stage;
}
#endif /* !IGNORE_FINDS */
// 设置返回值为0
ret_val = 0;
// 放弃当前条目
abandon_entry:
// 重置拼接目标ID
splicing_with = -1;
/* 如果我们通过了校准周期并且之前没有见过这个条目,更新待处理未测试计数。 */
// 如果没有停止信号,当前条目没有校准失败,且之前未被测试过
if (!stop_soon && !queue_cur->cal_failed && !queue_cur->was_fuzzed) {
// 标记当前条目为已测试
queue_cur->was_fuzzed = 1;
// 减少待处理未测试计数
pending_not_fuzzed--;
// 如果当前条目是优选的,减少优选计数
if (queue_cur->favored) pending_favored--;
}
// 取消映射原始输入缓冲区
munmap(orig_in, queue_cur->len);
// 如果in_buf不是原始输入缓冲区释放它
if (in_buf != orig_in) ck_free(in_buf);
// 释放输出缓冲区
ck_free(out_buf);
// 释放效果映射缓冲区
ck_free(eff_map);
// 返回结果
return ret_val;
#undef FLIP_BIT
}
/* 从其他模糊测试器中获取有趣的测试用例。 */
// 这个函数用于在分布式模糊测试环境中,从其他模糊测试器中同步测试用例。
static void sync_fuzzers(char** argv) {
DIR* sd; // 指向同步目录的目录流
struct dirent* sd_ent; // 目录流中的当前条目
u32 sync_cnt = 0; // 同步的模糊测试器数量
// 打开同步目录
sd = opendir(sync_dir);
if (!sd) PFATAL("Unable to open '%s'", sync_dir);
// 重置阶段最大值和当前值,以及当前深度
stage_max = stage_cur = 0;
cur_depth = 0;
/* 查看同步目录中为每个其他模糊测试器创建的条目。 */
// 遍历同步目录中的每个条目
while ((sd_ent = readdir(sd))) {
static u8 stage_tmp[128]; // 临时阶段名称
DIR* qd; // 指向队列目录的目录流
struct dirent* qd_ent; // 队列目录中的当前条目
u8 *qd_path, *qd_synced_path; // 队列目录和同步目录的路径
u32 min_accept = 0, next_min_accept; // 最小接受的测试用例ID和下一个最小接受的测试用例ID
s32 id_fd; // 用于存储最后看到的测试用例ID的文件的文件描述符
/* 跳过隐藏文件和我们自己的输出目录。 */
// 如果条目是隐藏文件或与我们自己的同步ID相同则跳过
if (sd_ent->d_name[0] == '.' || !strcmp(sync_id, sd_ent->d_name)) continue;
/* 跳过任何没有queue/子目录的东西。 */
// 构造队列目录的路径
qd_path = alloc_printf("%s/%s/queue", sync_dir, sd_ent->d_name);
// 打开队列目录
if (!(qd = opendir(qd_path))) {
ck_free(qd_path);
continue;
}
/* 检索最后看到的测试用例的ID。 */
// 构造同步目录中用于存储最后看到的测试用例ID的文件的路径
qd_synced_path = alloc_printf("%s/.synced/%s", out_dir, sd_ent->d_name);
// 打开或创建用于存储最后看到的测试用例ID的文件
id_fd = open(qd_synced_path, O_RDWR | O_CREAT, 0600);
// 如果打开文件失败,则输出错误信息并退出
if (id_fd < 0) PFATAL("Unable to create '%s'", qd_synced_path);
// 如果文件中已经有数据则读取最小接受的测试用例ID
if (read(id_fd, &min_accept, sizeof(u32)) > 0)
lseek(id_fd, 0, SEEK_SET);
// 更新下一个最小接受的测试用例ID
next_min_accept = min_accept;
/* 显示统计信息 */
// 设置阶段名称和当前阶段值
sprintf(stage_tmp, "sync %u", ++sync_cnt);
stage_name = stage_tmp;
stage_cur = 0;
stage_max = 0;
/* 对于这个模糊测试器排队的每个文件解析ID并查看我们是否之前已经看过它
如果没有,执行测试用例。 */
// 遍历队列目录中的每个条目
while ((qd_ent = readdir(qd))) {
u8* path; // 文件的路径
s32 fd; // 文件描述符
struct stat st; // 文件状态
// 如果条目是隐藏文件或测试用例ID小于最小接受的测试用例ID则跳过
if (qd_ent->d_name[0] == '.' ||
sscanf(qd_ent->d_name, CASE_PREFIX "%06u", &syncing_case) != 1 ||
syncing_case < min_accept) continue;
/* 好的,听起来像是一个新测试用例。让我们试试它。 */
// 如果测试用例ID大于或等于下一个最小接受的测试用例ID
if (syncing_case >= next_min_accept)
next_min_accept = syncing_case + 1;
// 构造文件的路径
path = alloc_printf("%s/%s", qd_path, qd_ent->d_name);
/* 允许在其他模糊测试器正在恢复等情况下失败... */
// 打开文件
fd = open(path, O_RDONLY);
// 如果打开文件失败,则释放路径内存并继续
if (fd < 0) {
ck_free(path);
continue;
}
// 获取文件状态
if (fstat(fd, &st)) PFATAL("fstat() failed");
/* 忽略大小为零或过大的文件。 */
// 如果文件大小在允许范围内
if (st.st_size && st.st_size <= MAX_FILE) {
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);
/* 看看会发生什么。我们依赖save_if_interesting()来捕获主要
错误并保存测试用例。 */
// 将测试用例写入测试用例缓冲区
write_to_testcase(mem, st.st_size);
// 运行目标程序并获取故障标志
fault = run_target(argv, exec_tmout);
// 如果收到停止信号,则返回
if (stop_soon) return;
// 设置当前正在同步的模糊测试器的名称
syncing_party = sd_ent->d_name;
// 将测试用例添加到队列中
queued_imported += save_if_interesting(argv, mem, st.st_size, fault);
// 重置当前正在同步的模糊测试器的名称
// 取消内存映射并释放内存
munmap(mem, st.st_size);
// 如果需要,则显示统计信息
if (!(stage_cur++ % stats_update_freq)) show_stats();
}
// 释放路径内存并关闭文件描述符
ck_free(path);
close(fd);
}
// 将下一个最小接受的测试用例ID写入文件
ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path);
// 关闭文件描述符
close(id_fd);
// 关闭队列目录流
closedir(qd);
// 释放队列目录路径内存
ck_free(qd_path);
// 释放同步目录路径内存
ck_free(qd_synced_path);
}
// 关闭同步目录流
closedir(sd);
}
/* 处理停止信号Ctrl-C等。 */
// 这个函数用于处理停止信号例如用户按下Ctrl-C。
static void handle_stop_sig(int sig) {
// 设置停止标志
stop_soon = 1;
// 如果子进程存在,则杀死它
if (child_pid > 0) kill(child_pid, SIGKILL);
// 如果fork服务器进程存在则杀死它
if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL);
}
/* 处理跳过请求SIGUSR1。 */
// 这个函数用于处理跳过请求信号。
static void handle_skipreq(int sig) {
// 设置跳过请求标志
skip_requested = 1;
}
/* 处理超时SIGALRM。 */
// 这个函数用于处理超时信号。
static void handle_timeout(int sig) {
// 如果子进程存在,则标记它为超时并杀死它
if (child_pid > 0) {
child_timed_out = 1;
kill(child_pid, SIGKILL);
} else if (child_pid == -1 && forksrv_pid > 0) {
// 如果子进程不存在但fork服务器进程存在则标记它为超时并杀死它
child_timed_out = 1;
kill(forksrv_pid, SIGKILL);
}
}
// 检查目标二进制文件是否存在、是否可执行等属性的函数
void check_binary(u8* fname) {
u8* env_path = 0; // 环境变量PATH
struct stat st; // 文件状态结构体
s32 fd; // 文件描述符
u8* f_data; // 文件数据
u32 f_len = 0; // 文件长度
ACTF("Validating target binary..."); // 动作提示:验证目标二进制文件
// 如果文件名中包含路径分隔符'/'或者环境变量PATH未设置则直接使用文件名
if (strchr(fname, '/') || !(env_path = getenv("PATH"))) {
target_path = ck_strdup(fname); // 复制文件名
// 检查文件是否存在、是否为普通文件、是否可执行、文件长度是否至少为4字节
if (stat(target_path, &st) || !S_ISREG(st.st_mode) ||
!(st.st_mode & 0111) || (f_len = st.st_size) < 4)
FATAL("Program '%s' not found or not executable", fname); // 如果检查失败,输出错误信息并退出
} else {
// 如果环境变量PATH已设置则遍历PATH中的每个目录
while (env_path) {
u8 *cur_elem, *delim = strchr(env_path, ':'); // 查找路径分隔符':'
if (delim) {
// 如果找到分隔符,则复制当前目录到新分配的内存
cur_elem = ck_alloc(delim - env_path + 1);
memcpy(cur_elem, env_path, delim - env_path);
delim++;
} else cur_elem = ck_strdup(env_path); // 如果没有分隔符,复制剩余的路径
env_path = delim; // 更新env_path指针
if (cur_elem[0])
target_path = alloc_printf("%s/%s", cur_elem, fname); // 构造完整的文件路径
else
target_path = ck_strdup(fname); // 如果当前目录为空,则直接使用文件名
ck_free(cur_elem); // 释放临时内存
// 如果找到文件并且文件属性符合要求,则跳出循环
if (!stat(target_path, &st) && S_ISREG(st.st_mode) &&
(st.st_mode & 0111) && (f_len = st.st_size) >= 4) break;
ck_free(target_path); // 释放之前分配的内存
target_path = 0; // 重置target_path
}
if (!target_path) FATAL("Program '%s' not found or not executable", fname); // 如果未找到文件,输出错误信息并退出
}
// 如果环境变量AFL_SKIP_BIN_CHECK被设置则跳过后续检查
if (getenv("AFL_SKIP_BIN_CHECK")) return;
/* 检查用户是否犯了一些明显的错误,比如将二进制文件放在/tmp或/var/tmp目录下 */
if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) ||
(!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/')))
FATAL("Please don't keep binaries in /tmp or /var/tmp");
// 打开目标文件
fd = open(target_path, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", target_path); // 如果打开失败,输出错误信息
// 将文件内容映射到内存
f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0);
if (f_data == MAP_FAILED) PFATAL("Unable to mmap file '%s'", target_path); // 如果映射失败,输出错误信息
close(fd); // 关闭文件描述符
// 检查文件是否为脚本文件
if (f_data[0] == '#' && f_data[1] == '!') {
// 如果是脚本文件,输出错误信息并退出
SAYF("\n" cLRD "[-] " cRST "Oops, the target binary looks like a shell script...");
FATAL("Program '%s' is a shell script", target_path);
}
#ifndef __APPLE__
// 检查文件是否为ELF格式
if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3))
FATAL("Program '%s' is not an ELF binary", target_path);
#else
// 在苹果系统上检查文件是否为Mach-O格式
if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED)
FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path);
#endif /* ^!__APPLE__ */
// 如果没有使用QEMU模式且没有使用dumb模式检查文件是否被AFL插桩
if (!qemu_mode && !dumb_mode &&
!memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) {
// 如果没有插桩,输出错误信息并退出
SAYF("\n" cLRD "[-] " cRST "Looks like the target binary is not instrumented!...");
FATAL("No instrumentation detected");
}
if (qemu_mode &&
memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) {
// 如果在QEMU模式下检测到插桩输出错误信息并退出
SAYF("\n" cLRD "[-] " cRST "This program appears to be instrumented with afl-gcc...");
FATAL("Instrumentation found in -Q mode");
}
// 检查文件是否使用了AddressSanitizer
if (memmem(f_data, f_len, "libasan.so", 10) ||
memmem(f_data, f_len, "__msan_init", 11)) uses_asan = 1;
/* 检测二进制文件中的持久模式和延迟初始化签名 */
if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) {
OKF(cPIN "Persistent mode binary detected.");
setenv(PERSIST_ENV_VAR, "1", 1);
persistent_mode = 1;
} else if (getenv("AFL_PERSISTENT")) {
WARNF("AFL_PERSISTENT is no longer supported and may misbehave!");
}
if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) {
OKF(cPIN "Deferred forkserver binary detected.");
setenv(DEFER_ENV_VAR, "1", 1);
deferred_mode = 1;
} else if (getenv("AFL_DEFER_FORKSRV")) {
WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!");
}
if (munmap(f_data, f_len)) PFATAL("unmap() failed"); // 取消内存映射
}
// 修剪并可能为运行创建一个横幅
static void fix_up_banner(u8* name) {
// 如果没有设置横幅则根据同步ID或文件名来设置
if (!use_banner) {
if (sync_id) {
use_banner = sync_id;
} else {
u8* trim = strrchr(name, '/'); // 查找文件名中的路径分隔符
if (!trim) use_banner = name; else use_banner = trim + 1;
}
}
// 如果横幅字符串过长,则截断它
if (strlen(use_banner) > 40) {
u8* tmp = ck_alloc(44);
sprintf(tmp, "%.40s...", use_banner);
use_banner = tmp;
}
}
// 检查是否在TTY上运行
static void check_if_tty(void) {
struct winsize ws; // 窗口大小结构体
// 如果设置了环境变量AFL_NO_UI则禁用UI
if (getenv("AFL_NO_UI")) {
OKF("Disabling the UI because AFL_NO_UI is set.");
not_on_tty = 1;
return;
}
// 如果无法获取窗口大小则认为不在TTY上运行
if (ioctl(1, TIOCGWINSZ, &ws)) {
if (errno == ENOTTY) {
OKF("Looks like we're not running on a tty, so I'll be a bit less verbose.");
not_on_tty = 1;
}
return;
}
}
/* 在终端尺寸变化后检查终端尺寸。 */
// 这个函数检查终端的尺寸,以确保它不是太小,从而无法适当地显示程序的输出。
static void check_term_size(void) {
struct winsize ws; // winsize结构体用于存储终端的尺寸信息
term_too_small = 0; // 假设终端不是太小
// 使用ioctl系统调用来获取终端的尺寸信息
if (ioctl(1, TIOCGWINSZ, &ws)) return; // 如果ioctl调用失败则返回
// 如果窗口尺寸的行数或列数为0或者小于某个阈值则认为终端太小
if (ws.ws_row == 0 && ws.ws_col == 0) return; // 如果行数和列数都为0则返回
if (ws.ws_row < 25 || ws.ws_col < 80) term_too_small = 1; // 设置终端太小的标志
}
/* 显示使用提示。 */
// 这个函数在用户请求帮助或者使用了错误的命令行参数时显示程序的使用提示。
static void usage(u8* argv0) {
// 使用SAYF宏来格式化并输出使用提示信息
SAYF("\n%s [ options ] -- /path/to/fuzzed_app [ ... ]\n\n"
"Required parameters:\n\n"
" -i dir - input directory with test cases\n"
" -o dir - output directory for fuzzer findings\n\n"
"Execution control settings:\n\n"
" -f file - location read by the fuzzed program (stdin)\n"
" -t msec - timeout for each run (auto-scaled, 50-%u ms)\n"
" -m megs - memory limit for child process (%u MB)\n"
" -Q - use binary-only instrumentation (QEMU mode)\n\n"
"Fuzzing behavior settings:\n\n"
" -d - quick & dirty mode (skips deterministic steps)\n"
" -n - fuzz without instrumentation (dumb mode)\n"
" -x dir - optional fuzzer dictionary (see README)\n\n"
"Other stuff:\n\n"
" -T text - text banner to show on the screen\n"
" -M / -S id - distributed mode (see parallel_fuzzing.txt)\n"
" -C - crash exploration mode (the peruvian rabbit thing)\n"
" -V - show version number and exit\n\n"
" -b cpu_id - bind the fuzzing process to the specified CPU core\n\n"
"For additional tips, please consult %s/README.\n\n",
argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); // 使用宏替换标记来插入特定的值
// 显示使用提示后退出程序
exit(1);
}
/* Prepare output directories and fds. */
/* 准备输出目录和文件描述符。 */
EXP_ST void setup_dirs_fds(void) {
u8* tmp; // 临时字符串指针
s32 fd; // 文件描述符
ACTF("Setting up output directories..."); // 动作提示:设置输出目录
// 如果设置了同步ID尝试创建同步目录如果失败且不是因为已存在则输出错误信息并终止
if (sync_id && mkdir(sync_dir, 0700) && errno != EEXIST)
PFATAL("Unable to create '%s'", sync_dir);
// 尝试创建输出目录,如果失败且不是因为已存在,则输出错误信息并终止
if (mkdir(out_dir, 0700)) {
if (errno != EEXIST) PFATAL("Unable to create '%s'", out_dir);
maybe_delete_out_dir(); // 可能删除已存在的输出目录
} else {
if (in_place_resume)
FATAL("Resume attempted but old output directory not found"); // 如果尝试在地恢复,但未找到旧的输出目录,则终止
out_dir_fd = open(out_dir, O_RDONLY); // 打开输出目录
#ifndef __sun
// 如果无法锁定输出目录,则输出错误信息并终止
if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB))
PFATAL("Unable to flock() output directory.");
#endif /* !__sun */
}
// 创建队列目录,用于存放起始和发现的路径
tmp = alloc_printf("%s/queue", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建队列元数据的顶级目录,用于会话恢复等任务
tmp = alloc_printf("%s/queue/.state/", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建目录,用于标记已经过确定性测试的队列条目
tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建目录,用于存放自动选择的字典条目
tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建目录,用于标记当前认为多余的路径集
tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建目录,用于标记显示变量行为的路径集
tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 如果设置了同步ID创建同步目录用于跟踪合作的模糊测试器
if (sync_id) {
tmp = alloc_printf("%s/.synced/", out_dir);
if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST))
PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
}
// 创建目录,用于存放所有记录的崩溃
tmp = alloc_printf("%s/crashes", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建目录,用于存放所有记录的挂起
tmp = alloc_printf("%s/hangs", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
// 创建一般有用的文件描述符
dev_null_fd = open("/dev/null", O_RDWR); // 打开null设备文件描述符
if (dev_null_fd < 0) PFATAL("Unable to open /dev/null");
dev_urandom_fd = open("/dev/urandom", O_RDONLY); // 打开随机设备文件描述符
if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom");
// 创建Gnuplot输出文件
tmp = alloc_printf("%s/plot_data", out_dir);
fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); // 打开或创建plot_data文件
if (fd < 0) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);
plot_file = fdopen(fd, "w"); // 将文件描述符与FILE*关联
if (!plot_file) PFATAL("fdopen() failed"); // 如果失败,则输出错误信息并终止
// 写入Gnuplot输出文件的标题行
fprintf(plot_file, "# unix_time, cycles_done, cur_path, paths_total, "
"pending_total, pending_favs, map_size, unique_crashes, "
"unique_hangs, max_depth, execs_per_sec\n");
/* ignore errors */
}
/* 如果没有使用-f选项则设置模糊测试数据的输出文件。 */
EXP_ST void setup_stdio_file(void) {
u8* fn = alloc_printf("%s/.cur_input", out_dir); // 构造当前输入文件的路径
unlink(fn); // 删除已存在的当前输入文件,忽略错误
out_fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600); // 打开或创建当前输入文件
if (out_fd < 0) PFATAL("Unable to create '%s'", fn); // 如果失败,则输出错误信息并终止
ck_free(fn); // 释放路径字符串
}
/* 确保核心转储core dumps不会发送到外部程序。 */
static void check_crash_handling(void) {
#ifdef __APPLE__
/* 在Mac OS X上似乎没有简单的C API可以查询已加载的守护进程状态我也不愿意在没有测试环境的情况下
做一些更复杂的操作比如通过Mach端口禁用崩溃报告。因此目前我们用一种不太优雅的方式来检查崩溃报告。 */
// 尝试执行系统命令来检查是否有崩溃报告被配置为发送到外部程序
if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'")) return;
// 如果系统配置了发送崩溃通知到外部程序,输出警告信息
SAYF("\n" cLRD "[-] " cRST
"Whoops, your system is configured to forward crash notifications to an\n"
" external crash reporting utility. This will cause issues due to the\n"
" extended delay between the fuzzed binary malfunctioning and this fact\n"
" being relayed to the fuzzer via the standard waitpid() API.\n\n"
" To avoid having crashes misinterpreted as timeouts, please run the\n"
" following commands:\n\n"
" SL=/System/Library; PL=com.apple.ReportCrash\n"
" launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n"
" sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n");
// 如果没有设置环境变量来忽略缺失的崩溃报告,则终止程序
if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"))
FATAL("Crash reporter detected");
#else
/* 这是Linux特定的代码我不认为*BSD上有等效的设置所以我们暂时可以忽略这个问题。 */
// 打开核心转储模式文件
s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY);
u8 fchar;
// 如果打开文件失败,则直接返回
if (fd < 0) return;
// 输出动作信息:正在检查核心转储模式
ACTF("Checking core_pattern...");
// 读取核心转储模式文件的第一个字符
if (read(fd, &fchar, 1) == 1 && fchar == '|') {
// 如果第一个字符是管道符号(|),说明系统配置了将核心转储发送到外部程序
SAYF("\n" cLRD "[-] " cRST
"Hmm, your system is configured to send core dump notifications to an\n"
" external utility. This will cause issues: there will be an extended delay\n"
" between stumbling upon a crash and having this information relayed to the\n"
" fuzzer via the standard waitpid() API.\n\n"
" To avoid having crashes misinterpreted as timeouts, please log in as root\n"
" and temporarily modify /proc/sys/kernel/core_pattern, like so:\n\n"
" echo core >/proc/sys/kernel/core_pattern\n");
// 如果没有设置环境变量来忽略缺失的崩溃报告,则终止程序
if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"))
FATAL("Pipe at the beginning of 'core_pattern'");
}
// 关闭核心转储模式文件
close(fd);
#endif /* ^__APPLE__ */
}
/* 检查CPU调速器scaling governor。 */
static void check_cpu_governor(void) {
FILE* f; // 文件指针
u8 tmp[128]; // 临时缓冲区
u64 min = 0, max = 0; // 最小和最大CPU频率
// 如果设置了环境变量AFL_SKIP_CPUFREQ则跳过此检查
if (getenv("AFL_SKIP_CPUFREQ")) return;
// 尝试打开CPU调速器配置文件
f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r");
if (!f) return; // 如果打开失败,则跳过此检查
ACTF("Checking CPU scaling governor..."); // 输出动作信息
// 读取CPU调速器配置
if (!fgets(tmp, 128, f)) PFATAL("fgets() failed"); // 如果读取失败,则输出错误信息并终止
fclose(f); // 关闭文件
// 如果CPU调速器设置为performance则不需要调整
if (!strncmp(tmp, "perf", 4)) return;
// 尝试打开最小CPU频率配置文件
f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r");
if (f) {
if (fscanf(f, "%llu", &min) != 1) min = 0; // 读取最小频率
fclose(f);
}
// 尝试打开最大CPU频率配置文件
f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r");
if (f) {
if (fscanf(f, "%llu", &max) != 1) max = 0; // 读取最大频率
fclose(f);
}
// 如果最小和最大频率相同,则不需要调整
if (min == max) return;
// 输出警告信息提示用户调整CPU调速器设置
SAYF("\n" cLRD "[-] " cRST
"Whoops, your system uses on-demand CPU frequency scaling, adjusted\n"
" between %llu and %llu MHz. Unfortunately, the scaling algorithm in the\n"
" kernel is imperfect and can miss the short-lived processes spawned by\n"
" afl-fuzz. To keep things moving, run these commands as root:\n\n"
" cd /sys/devices/system/cpu\n"
" echo performance | tee cpu*/cpufreq/scaling_governor\n\n"
" You can later go back to the original state by replacing 'performance' with\n"
" 'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ\n"
" to make afl-fuzz skip this check - but expect some performance drop.\n",
min / 1024, max / 1024);
FATAL("Suboptimal CPU scaling governor"); // 如果CPU调速器设置不合理则终止程序
}
/* 计算逻辑CPU核心数。 */
static void get_core_count(void) {
u32 cur_runnable = 0; // 当前可运行的任务数
#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__)
size_t s = sizeof(cpu_core_count); // 核心数的大小
/* 在*BSD系统上我们可以使用sysctl来获取CPU数量。 */
#ifdef __APPLE__
// 在Mac OS X上获取逻辑CPU核心数
if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0)
return;
#else
int s_name[2] = { CTL_HW, HW_NCPU }; // sysctl的名称
// 在其他*BSD系统上获取逻辑CPU核心数
if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) return;
#endif /* ^__APPLE__ */
#else
#ifdef HAVE_AFFINITY
// 如果支持CPU亲和性则使用sysconf获取逻辑CPU核心数
cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN);
#else
FILE* f = fopen("/proc/stat", "r"); // 尝试打开/proc/stat文件
u8 tmp[1024]; // 临时缓冲区
if (!f) return; // 如果打开失败,则跳过此检查
// 读取/proc/stat文件计算逻辑CPU核心数
while (fgets(tmp, sizeof(tmp), f))
if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) cpu_core_count++;
fclose(f); // 关闭文件
#endif /* ^HAVE_AFFINITY */
#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */
// 如果成功获取逻辑CPU核心数
if (cpu_core_count > 0) {
cur_runnable = (u32)get_runnable_processes(); // 获取当前可运行的任务数
#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__)
// 添加当前进程因为1分钟平均值尚未包括它
cur_runnable++;
#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */
// 输出CPU核心数和当前可运行的任务数
OKF("You have %u CPU core%s and %u runnable tasks (utilization: %0.0f%%).",
cpu_core_count, cpu_core_count > 1 ? "s" : "",
cur_runnable, cur_runnable * 100.0 / cpu_core_count);
if (cpu_core_count > 1) {
// 如果当前可运行的任务数超过CPU核心数的1.5倍,则输出警告信息
if (cur_runnable > cpu_core_count * 1.5) {
WARNF("System under apparent load, performance may be spotty.");
} else if (cur_runnable + 1 <= cpu_core_count) {
// 如果当前可运行的任务数加1小于或等于CPU核心数则输出提示信息
OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path);
}
}
} else {
cpu_core_count = 0; // 如果无法获取逻辑CPU核心数则设置为0
WARNF("Unable to figure out the number of CPU cores."); // 输出警告信息
}
}
/* 验证并修正使用-S时的out_dir和sync_dir。 */
static void fix_up_sync(void) {
u8* x = sync_id; // 同步ID
if (dumb_mode)
FATAL("-S / -M and -n are mutually exclusive"); // 如果同时使用-S/-M和-n则终止程序
if (skip_deterministic) {
if (force_deterministic)
FATAL("use -S instead of -M -d"); // 如果同时使用-M -d和-S则终止程序
else
FATAL("-S already implies -d"); // 如果使用-S则隐含-d不需要再次指定
}
// 检查同步ID是否只包含字母数字、下划线或破折号
while (*x) {
if (!isalnum(*x) && *x != '_' && *x != '-')
FATAL("Non-alphanumeric fuzzer ID specified via -S or -M");
x++;
}
// 如果同步ID太长则终止程序
if (strlen(sync_id) > 32) FATAL("Fuzzer ID too long");
// 构造同步目录路径
x = alloc_printf("%s/%s", out_dir, sync_id);
sync_dir = out_dir; // 设置同步目录
out_dir = x; // 设置输出目录
if (!force_deterministic) {
skip_deterministic = 1; // 跳过确定性测试
use_splicing = 1; // 使用拼接技术
}
}
/* 处理屏幕大小变化SIGWINCH。 */
static void handle_resize(int sig) {
clear_screen = 1; // 设置清除屏幕的标志
}
/* 检查ASAN选项。 */
static void check_asan_opts(void) {
u8* x = getenv("ASAN_OPTIONS"); // 获取ASAN_OPTIONS环境变量
if (x) {
// 如果ASAN_OPTIONS没有设置abort_on_error=1则终止程序
if (!strstr(x, "abort_on_error=1"))
FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!");
// 如果ASAN_OPTIONS没有设置symbolize=0则终止程序
if (!strstr(x, "symbolize=0"))
FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!");
}
x = getenv("MSAN_OPTIONS"); // 获取MSAN_OPTIONS环境
if (x) {
// 如果MSAN_OPTIONS环境变量被设置了检查是否包含了特定的exit_code值
if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR)))
FATAL("Custom MSAN_OPTIONS set without exit_code="
STRINGIFY(MSAN_ERROR) " - please fix!");
// 检查MSAN_OPTIONS是否设置了symbolize=0这通常用于防止asan_symbolize.py运行
if (!strstr(x, "symbolize=0"))
FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!");
}
}
/* 检测命令行参数中的'@@'符号并替换为文件路径。 */
EXP_ST void detect_file_args(char** argv) {
u32 i = 0; // 初始化索引变量
u8* cwd = getcwd(NULL, 0); // 获取当前工作目录
// 如果无法获取当前工作目录,输出错误信息并终止程序
if (!cwd) PFATAL("getcwd() failed");
// 遍历命令行参数
while (argv[i]) {
u8* aa_loc = strstr(argv[i], "@@"); // 查找参数中的'@@'符号
// 如果找到了'@@'符号
if (aa_loc) {
u8 *aa_subst, *n_arg;
/* 如果还没有指定输出文件名,则使用一个安全的默认值。 */
if (!out_file)
out_file = alloc_printf("%s/.cur_input", out_dir); // 使用默认输出文件名
/* 确保我们总是使用完全合格的路径。 */
if (out_file[0] == '/')
aa_subst = out_file; // 如果out_file是绝对路径则直接使用
else
aa_subst = alloc_printf("%s/%s", cwd, out_file); // 否则,构造绝对路径
/* 构造替换后的argv值。 */
*aa_loc = 0; // 临时终止字符串以构造新值
n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 构造新参数值
argv[i] = n_arg; // 更新argv
*aa_loc = '@'; // 恢复'@@'符号
if (out_file[0] != '/') ck_free(aa_subst); // 如果out_file不是绝对路径则释放构造的绝对路径
}
i++; // 移动到下一个参数
}
free(cwd); // 释放当前工作目录字符串
}
/* 设置信号处理器。Solaris上的libc比较复杂因为它在中断的read()调用时不会恢复,
当你调用siginterrupt()时会设置SA_RESETHAND并且会做一些不必要的事情。 */
EXP_ST void setup_signal_handlers(void) {
struct sigaction sa; // 定义信号动作结构体
// 初始化信号动作结构体
sa.sa_handler = NULL; // 没有指定信号处理函数
sa.sa_flags = SA_RESTART; // 设置信号处理时自动重启被中断的系统调用
sa.sa_sigaction = NULL; // 没有指定信号的特定处理函数
sigemptyset(&sa.sa_mask); // 清空信号集
/* 各种表示“停止”的信号。 */
// 设置信号处理函数为handle_stop_sig
sa.sa_handler = handle_stop_sig;
sigaction(SIGHUP, &sa, NULL); // 对SIGHUP信号进行设置
sigaction(SIGINT, &sa, NULL); // 对SIGINT信号进行设置
sigaction(SIGTERM, &sa, NULL); // 对SIGTERM信号进行设置
/* 执行超时通知。 */
// 设置信号处理函数为handle_timeout
sa.sa_handler = handle_timeout;
sigaction(SIGALRM, &sa, NULL); // 对SIGALRM信号进行设置
/* 窗口大小改变通知 */
// 设置信号处理函数为handle_resize
sa.sa_handler = handle_resize;
sigaction(SIGWINCH, &sa, NULL); // 对SIGWINCH信号进行设置
/* SIGUSR1: 跳过条目 */
// 设置信号处理函数为handle_skipreq
sa.sa_handler = handle_skipreq;
sigaction(SIGUSR1, &sa, NULL); // 对SIGUSR1信号进行设置
/* 我们不关心的信号。 */
// 设置信号处理函数为SIG_IGN忽略这些信号
sa.sa_handler = SIG_IGN;
sigaction(SIGTSTP, &sa, NULL); // 对SIGTSTP信号进行设置
sigaction(SIGPIPE, &sa, NULL); // 对SIGPIPE信号进行设置
}
/* 为QEMU重写argv。 */
static char** get_qemu_argv(u8* own_loc, char** argv, int argc) {
char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); // 分配新的argv数组
u8 *tmp, *cp, *rsl, *own_copy;
/* 针对QEMU稳定性问题的工作区。 */
setenv("QEMU_LOG", "nochain", 1); // 设置环境变量QEMU_LOG
// 将原始argv的参数复制到新数组中从第三个参数开始
memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc);
new_argv[2] = target_path; // 设置目标路径
new_argv[1] = "--"; // 设置参数分隔符
/* 现在我们需要找到QEMU二进制文件并放入argv[0]。 */
tmp = getenv("AFL_PATH"); // 获取环境变量AFL_PATH
if (tmp) {
cp = alloc_printf("%s/afl-qemu-trace", tmp); // 构造QEMU路径
if (access(cp, X_OK)) // 检查文件是否存在且可执行
FATAL("Unable to find '%s'", tmp); // 如果找不到,输出错误信息并终止
target_path = new_argv[0] = cp; // 设置目标路径和argv[0]
return new_argv; // 返回新的argv数组
}
own_copy = ck_strdup(own_loc); // 复制原始位置信息
rsl = strrchr(own_copy, '/'); // 查找路径分隔符
if (rsl) {
*rsl = 0; // 将路径分隔符替换为字符串结束符
cp = alloc_printf("%s/afl-qemu-trace", own_copy); // 构造QEMU路径
ck_free(own_copy); // 释放原始位置信息
if (!access(cp, X_OK)) { // 检查文件是否存在且可执行
target_path = new_argv[0] = cp; // 设置目标路径和argv[0]
return new_argv; // 返回新的argv数组
}
} else ck_free(own_copy); // 如果没有找到路径分隔符,释放原始位置信息
if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { // 检查默认路径下的QEMU是否存在且可执行
target_path = new_argv[0] = ck_strdup(BIN_PATH "/afl-qemu-trace"); // 设置目标路径和argv[0]
return new_argv; // 返回新的argv数组
}
SAYF("\n" cLRD "[-] " cRST // 输出错误信息
"Oops, unable to find the 'afl-qemu-trace' binary. The binary must be built\n"
" separately by following the instructions in qemu_mode/README.qemu. If you\n"
" already have the binary installed, you may need to specify AFL_PATH in the\n"
" environment.\n\n"
" Of course, even without QEMU, afl-fuzz can still work with binaries that are\n"
" instrumented at compile time with afl-gcc. It is also possible to use it as a\n"
" traditional \"dumb\" fuzzer by specifying '-n' in the command line.\n");
FATAL("Failed to locate 'afl-qemu-trace'."); // 输出错误信息并终止
}
/* 保存当前命令行参数。 */
static void save_cmdline(u32 argc, char** argv) {
u32 len = 1, i; // 初始化长度变量
u8* buf; // 定义缓冲区指针
// 计算命令行参数的总长度
for (i = 0; i < argc; i++)
len += strlen(argv[i]) + 1;
buf = orig_cmdline = ck_alloc(len); // 分配缓冲区
// 将命令行参数复制到缓冲区
for (i = 0; i < argc; i++) {
u32 l = strlen(argv[i]);
memcpy(buf, argv[i], l);
buf += l;
if (i != argc - 1) *(buf++) = ' '; // 在参数之间添加空格
}
*buf = 0; // 设置字符串结束符
}
#ifndef AFL_LIB
/* Main entry point */
int main(int argc, char** argv) {
// 定义了一些变量,用于存储命令行参数和状态
s32 opt;
u64 prev_queued = 0;
u32 sync_interval_cnt = 0, seek_to;
u8 *extras_dir = 0;
u8 mem_limit_given = 0;
u8 exit_1 = !!getenv("AFL_BENCH_JUST_ONE");
char** use_argv;
// 定义时间相关的结构体
struct timeval tv;
struct timezone tz;
// 打印欢迎信息和版本号
SAYF(cCYA "afl-fuzz " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
// 设置文档路径
doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH;
// 获取当前时间,用于随机数种子
gettimeofday(&tv, &tz);
srandom(tv.tv_sec ^ tv.tv_usec ^ getpid());
// 解析命令行参数
while ((opt = getopt(argc, argv, "+i:o:f:m:b:t:T:dnCB:S:M:x:QV")) > 0) {
switch (opt) {
// 处理不同的命令行选项
case 'i': /* input dir */
// 设置输入目录
if (in_dir) FATAL("Multiple -i options not supported");
in_dir = optarg;
if (!strcmp(in_dir, "-")) in_place_resume = 1;
break;
case 'o': /* output dir */
// 设置输出目录
if (out_dir) FATAL("Multiple -o options not supported");
out_dir = optarg;
break;
case 'M': { /* master sync ID */
// 设置主同步ID
u8* c;
if (sync_id) FATAL("Multiple -S or -M options not supported");
sync_id = ck_strdup(optarg);
if ((c = strchr(sync_id, ':'))) {
*c = 0;
if (sscanf(c + 1, "%u/%u", &master_id, &master_max) != 2 ||
!master_id || !master_max || master_id > master_max ||
master_max > 1000000) FATAL("Bogus master ID passed to -M");
}
force_deterministic = 1;
}
break;
case 'S':
// 设置同步ID
if (sync_id) FATAL("Multiple -S or -M options not supported");
sync_id = ck_strdup(optarg);
break;
case 'f': /* target file */
// 设置目标文件
if (out_file) FATAL("Multiple -f options not supported");
out_file = optarg;
break;
case 'x': /* dictionary */
// 设置额外的字典目录
if (extras_dir) FATAL("Multiple -x options not supported");
extras_dir = optarg;
break;
case 't': { /* timeout */
// 设置超时时间
u8 suffix = 0;
if (timeout_given) FATAL("Multiple -t options not supported");
if (sscanf(optarg, "%u%c", &exec_tmout, &suffix) < 1 ||
optarg[0] == '-') FATAL("Bad syntax used for -t");
if (exec_tmout < 5) FATAL("Dangerously low value of -t");
if (suffix == '+') timeout_given = 2; else timeout_given = 1;
break;
}
case 'm': { /* mem limit */
// 设置内存限制
u8 suffix = 'M';
if (mem_limit_given) FATAL("Multiple -m options not supported");
mem_limit_given = 1;
if (!strcmp(optarg, "none")) {
mem_limit = 0;
break;
}
if (sscanf(optarg, "%llu%c", &mem_limit, &suffix) < 1 ||
optarg[0] == '-') FATAL("Bad syntax used for -m");
switch (suffix) {
case 'T': mem_limit *= 1024 * 1024; break;
case 'G': mem_limit *= 1024; break;
case 'k': mem_limit /= 1024; break;
case 'M': break;
default: FATAL("Unsupported suffix or bad syntax for -m");
}
if (mem_limit < 5) FATAL("Dangerously low value of -m");
if (sizeof(rlim_t) == 4 && mem_limit > 2000)
FATAL("Value of -m out of range on 32-bit systems");
}
break;
case 'b': { /* bind CPU core */
// 绑定CPU核心
if (cpu_to_bind_given) FATAL("Multiple -b options not supported");
cpu_to_bind_given = 1;
if (sscanf(optarg, "%u", &cpu_to_bind) < 1 ||
optarg[0] == '-') FATAL("Bad syntax used for -b");
break;
}
case 'd': /* skip deterministic */
// 跳过确定性测试
if (skip_deterministic) FATAL("Multiple -d options not supported");
skip_deterministic = 1;
use_splicing = 1;
break;
case 'B': /* load bitmap */
// 加载比特图
if (in_bitmap) FATAL("Multiple -B options not supported");
in_bitmap = optarg;
read_bitmap(in_bitmap);
break;
case 'C': /* crash mode */
// 设置为崩溃模式
if (crash_mode) FATAL("Multiple -C options not supported");
crash_mode = FAULT_CRASH;
break;
case 'n': /* dumb mode */
// 设置为简单模式
if (dumb_mode) FATAL("Multiple -n options not supported");
if (getenv("AFL_DUMB_FORKSRV")) dumb_mode = 2; else dumb_mode = 1;
break;
case 'T': /* banner */
// 设置横幅
if (use_banner) FATAL("Multiple -T options not supported");
use_banner = optarg;
break;
case 'Q': /* QEMU mode */
// 设置为QEMU模式
if (qemu_mode) FATAL("Multiple -Q options not supported");
qemu_mode = 1;
if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU;
break;
case 'V': /* Show version number */
// 显示版本号并退出
exit(0);
default:
// 默认行为:显示使用说明
usage(argv[0]);
}
}
// 检查必要的参数是否已设置
if (optind == argc || !in_dir || !out_dir) usage(argv[0]);
// 设置信号处理程序
setup_signal_handlers();
check_asan_opts();
// 如果设置了同步ID修正同步设置
if (sync_id) fix_up_sync();
// 输入和输出目录不能相同
if (!strcmp(in_dir, out_dir))
FATAL("Input and output directories can't be the same");
// 在简单模式下,检查互斥选项
if (dumb_mode) {
if (crash_mode) FATAL("-C and -n are mutually exclusive");
if (qemu_mode) FATAL("-Q and -n are mutually exclusive");
}
// 读取环境变量设置
if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1;
if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1;
if (getenv("AFL_NO_ARITH")) no_arith = 1;
if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1;
if (getenv("AFL_FAST_CAL")) fast_cal = 1;
// 设置挂起超时时间
if (getenv("AFL_HANG_TMOUT")) {
hang_tmout = atoi(getenv("AFL_HANG_TMOUT"));
if (!hang_tmout) FATAL("Invalid value of AFL_HANG_TMOUT");
}
// 检查互斥的环境变量设置
if (dumb_mode == 2 && no_forkserver)
FATAL("AFL_DUMB_FORKSRV and AFL_NO_FORKSRV are mutually exclusive");
// 设置预加载库
if (getenv("AFL_PRELOAD")) {
setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1);
}
// 如果设置了环境变量AFL_LD_PRELOAD则输出错误信息并终止程序。
if (getenv("AFL_LD_PRELOAD"))
FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD");
// 保存命令行参数,以便后续使用。
save_cmdline(argc, argv);
// 修复banner信息这通常是为了显示程序的版本号或其他信息。
fix_up_banner(argv[optind]);
// 检查当前是否是TTY环境这可能影响程序的输出方式。
check_if_tty();
// 获取CPU核心数这有助于后续的并行处理。
get_core_count();
#ifdef HAVE_AFFINITY
// 如果支持CPU亲和性设置则将程序绑定到一个空闲的CPU核心上。
bind_to_free_cpu();
#endif /* HAVE_AFFINITY */
// 检查崩溃处理设置,确保程序在崩溃时能够正确处理。
check_crash_handling();
check_cpu_governor();
// 设置后续操作,这可能包括日志文件的设置等。
setup_post();
setup_shm();
init_count_class16();
// 设置目录和文件描述符,为后续的文件操作做准备。
setup_dirs_fds();
read_testcases();
load_auto();
// 调整输入,这可能涉及到对测试用例的预处理。
pivot_inputs();
// 如果指定了extras目录则加载额外的测试用例。
if (extras_dir) load_extras(extras_dir);
// 如果没有给定超时时间,则自动寻找一个合适的超时时间。
if (!timeout_given) find_timeout();
// 检测文件参数,这可能涉及到对命令行参数的处理。
detect_file_args(argv + optind + 1);
// 如果没有指定输出文件,则设置标准输出文件。
if (!out_file) setup_stdio_file();
// 检查二进制文件,这可能涉及到对目标程序的验证。
check_binary(argv[optind]);
// 获取当前时间,用于后续的时间统计。
start_time = get_cur_time();
// 如果启用了QEMU模式则获取QEMU的命令行参数否则使用原始参数。
if (qemu_mode)
use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind);
else
use_argv = argv + optind;
// 执行一次干运行,以检查程序的行为。
perform_dry_run(use_argv);
// 修剪队列,移除无效或重复的测试用例。
cull_queue();
// 显示初始化统计信息。
show_init_stats();
// 寻找开始位置,这可能涉及到对测试用例的排序或选择。
seek_to = find_start_position();
// 写入统计文件,保存当前的状态。
write_stats_file(0, 0, 0);
save_auto();
// 如果程序即将停止,则跳转到停止处理部分。
if (stop_soon) goto stop_fuzzing;
// 如果不是在TTY环境下则等待一段时间再开始测试。
if (!not_on_tty) {
sleep(4);
start_time += 4000;
if (stop_soon) goto stop_fuzzing;
}
// 主循环开始,这里会不断执行模糊测试,直到程序停止。
while (1) {
u8 skipped_fuzz;
// 修剪队列,移除无效或重复的测试用例。
cull_queue();
// 如果队列中没有当前的测试用例,则进入下一个循环周期。
if (!queue_cur) {
queue_cycle++;
current_entry = 0;
cur_skipped_paths = 0;
queue_cur = queue;
// 如果需要seek到特定位置则移动队列指针。
while (seek_to) {
current_entry++;
seek_to--;
queue_cur = queue_cur->next;
}
// 显示统计信息。
show_stats();
// 如果不是在TTY环境下则输出当前的循环周期。
if (not_on_tty) {
ACTF("Entering queue cycle %llu.", queue_cycle);
fflush(stdout);
}
// 如果在当前循环周期中没有新的发现,则尝试重组策略。
if (queued_paths == prev_queued) {
if (use_splicing) cycles_wo_finds++; else use_splicing = 1;
} else cycles_wo_finds = 0;
// 更新之前的队列路径数。
prev_queued = queued_paths;
// 如果设置了同步ID并且是第一个循环周期并且设置了AFL_IMPORT_FIRST环境变量则同步模糊测试者。
if (sync_id && queue_cycle == 1 && getenv("AFL_IMPORT_FIRST"))
sync_fuzzers(use_argv);
}
// 执行一次模糊测试。
skipped_fuzz = fuzz_one(use_argv);
// 如果程序没有停止并且设置了同步ID并且没有跳过模糊测试则尝试同步模糊测试者。
if (!stop_soon && sync_id && !skipped_fuzz) {
if (!(sync_interval_cnt++ % SYNC_INTERVAL))
sync_fuzzers(use_argv);
}
// 如果程序即将退出并且设置了exit_1则设置停止标志。
if (!stop_soon && exit_1) stop_soon = 2;
// 如果程序即将停止,则跳出循环。
if (stop_soon) break;
// 移动到下一个测试用例。
queue_cur = queue_cur->next;
current_entry++;
}
// 如果队列中还有测试用例,则显示统计信息。
if (queue_cur) show_stats();
// 如果程序是被程序性地停止的那么杀死forkserver和当前的运行者。
// 如果是手动停止的,那么这部分由信号处理程序完成。
if (stop_soon == 2) {
if (child_pid > 0) kill(child_pid, SIGKILL);
if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL);
}
// 现在我们已经杀死了forkserver我们可以等待它以便获取资源使用统计信息。
if (waitpid(forksrv_pid, NULL, 0) <= 0) {
WARNF("error waitpid\n");
}
// 写入bitmap保存当前的测试覆盖情况。
write_bitmap();
// 写入统计文件,保存最终的状态。
write_stats_file(0, 0, 0);
// 保存自动保存的信息。
save_auto();
// 跳转到停止模糊测试的处理部分。
stop_fuzzing:
// 输出结束信息,显示测试是被程序性地停止还是被用户停止。
SAYF(CURSOR_SHOW cLRD "\n\n+++ Testing aborted %s +++\n" cRST,
stop_soon == 2 ? "programmatically" : "by user");
// 如果运行超过30分钟但仍然在第一个循环周期输出警告信息。
if (queue_cycle == 1 && get_cur_time() - start_time > 30 * 60 * 1000) {
SAYF("\n" cYEL "[!] " cRST
"Stopped during the first cycle, results may be incomplete.\n"
" (For info on resuming, see %s/README.)\n", doc_path);
}
// 关闭绘图文件,销毁队列和额外的测试用例,释放内存。
fclose(plot_file);
destroy_queue();
destroy_extras();
ck_free(target_path);
ck_free(sync_id);
// 生成报告。
alloc_report();
// 输出结束信息,表示测试完成。
OKF("We're done here. Have a nice day!\n");
// 退出程序。
exit(0);
}
#endif /* !AFL_LIB */