/* Copyright 2020 Google LLC 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 https://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. */ #define _CRT_SECURE_NO_WARNINGS #include #include #include "common.h" #include "sample.h" #include "fuzzer.h" #include "sampledelivery.h" #include "instrumentation.h" #include "coverage.h" #include "mutator.h" #include "thread.h" #include "directory.h" #include "client.h" #include "mersenne.h" using namespace std; void Fuzzer::PrintUsage() { printf("Incorrect usage, please refer to the documentation\n"); exit(0); } void Fuzzer::ParseOptions(int argc, char **argv) { int server_update_interval = GetIntOption("-server_update_interval", argc, argv, 5 * 60); server_update_interval_ms = server_update_interval * 1000; acceptable_hang_ratio = 0.01; acceptable_crash_ratio = 0.02; num_threads = 1; char *option; save_hangs = GetBinaryOption("-save_hangs", argc, argv, false); option = GetOption("-in", argc, argv); if (!option) PrintUsage(); this->in_dir = option; option = GetOption("-out", argc, argv); if (!option) PrintUsage(); this->out_dir = option; option = GetOption("-delivery_dir", argc, argv); if (!option) { delivery_dir = out_dir; } else { delivery_dir = option; } num_threads = GetIntOption("-nthreads", argc, argv, 1); int target_opt_ind = 0; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--") == 0) { target_opt_ind = i + 1; break; } } char *cmd = NULL; if (target_opt_ind) { target_argc = argc - target_opt_ind; target_argv = argv + target_opt_ind; } else { target_argc = 0; target_argv = NULL; } timeout = GetIntOption("-t", argc, argv, 0x7FFFFFFF); init_timeout = GetIntOption("-t1", argc, argv, timeout); corpus_timeout = GetIntOption("-t_corpus", argc, argv, timeout); if (GetOption("-server", argc, argv)) { server = new CoverageClient(); server->Init(argc, argv); } else { server = NULL; } should_restore_state = false; if((in_dir == "-") || GetBinaryOption("-restore", argc, argv, false) || GetBinaryOption("-resume", argc, argv, false)) { should_restore_state = true; } clean_target_on_coverage = GetBinaryOption("-clean_target_on_coverage", argc, argv, true); coverage_reproduce_retries = GetIntOption("-coverage_retry", argc, argv, DEFAULT_COVERAGE_REPRODUCE_RETRIES); crash_reproduce_retries = GetIntOption("-crash_retry", argc, argv, DEFAULT_CRASH_REPRODUCE_RETRIES); minimize_samples = GetBinaryOption("-minimize_samples", argc, argv, true); keep_samples_in_memory = GetBinaryOption("-keep_samples_in_memory", argc, argv, true); track_ranges = GetBinaryOption("-track_ranges", argc, argv, false); Sample::max_size = (size_t)GetIntOption("-max_sample_size", argc, argv, DEFAULT_MAX_SAMPLE_SIZE); dry_run = GetBinaryOption("-dry_run", argc, argv, false); incremental_coverage = GetBinaryOption("-incremental_coverage", argc, argv, true); add_all_inputs = GetBinaryOption("-add_all_inputs", argc, argv, false); dump_coverage = GetBinaryOption("-dump_coverage", argc, argv, false); skip_initial_server_sync = GetBinaryOption("-skip_initial_server_sync", argc, argv, skip_initial_server_sync); } void Fuzzer::SetupDirectories() { //create output directories CreateDirectory(out_dir); crash_dir = DirJoin(out_dir, "crashes"); CreateDirectory(crash_dir); hangs_dir = DirJoin(out_dir, "hangs"); CreateDirectory(hangs_dir); sample_dir = DirJoin(out_dir, "samples"); CreateDirectory(sample_dir); } void *StartFuzzThread(void *arg) { Fuzzer::ThreadContext *tc = (Fuzzer::ThreadContext*)arg; tc->fuzzer->RunFuzzerThread(tc); return NULL; } Fuzzer::ThreadContext::~ThreadContext() { if (sampleDelivery) delete sampleDelivery; if (prng) delete prng; if (mutator) delete mutator; if (instrumentation) delete instrumentation; if (target_argv) free(target_argv); } void Fuzzer::Run(int argc, char **argv) { if (GetOption("-start_server", argc, argv)) { // run the server printf("Running as server\n"); CoverageServer server; server.Init(argc, argv); server.RunServer(); return; } printf("Fuzzer version 1.00\n"); samples_pending = 0; num_crashes = 0; num_unique_crashes = 0; num_hangs = 0; num_samples = 0; num_samples_discarded = 0; total_execs = 0; ParseOptions(argc, argv); SetupDirectories(); if(should_restore_state) { state = RESTORE_NEEDED; } else { GetFilesInDirectory(in_dir, input_files); if (input_files.size() == 0) { WARN("Input directory is empty\n"); } else { SAY("%d input files read\n", (int)input_files.size()); } state = INPUT_SAMPLE_PROCESSING; } last_save_time = GetCurTime(); for (int i = 1; i <= num_threads; i++) { ThreadContext *tc = CreateThreadContext(argc, argv, i); CreateThread(StartFuzzThread, tc); } uint64_t last_execs = 0; uint32_t secs_to_sleep = 1; while (1) { #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) Sleep(secs_to_sleep * 1000); #else usleep(secs_to_sleep * 1000000); #endif size_t num_offsets = 0; coverage_mutex.Lock(); for(auto iter = fuzzer_coverage.begin(); iter != fuzzer_coverage.end(); iter++) { num_offsets += iter->offsets.size(); } coverage_mutex.Unlock(); printf("\nTotal execs: %lld\nUnique samples: %lld (%lld discarded)\nCrashes: %lld (%lld unique)\nHangs: %lld\nOffsets: %zu\nExecs/s: %lld\n", total_execs, num_samples, num_samples_discarded, num_crashes, num_unique_crashes, num_hangs, num_offsets, (total_execs - last_execs) / secs_to_sleep); last_execs = total_execs; if (state == FUZZING && dry_run) { printf("\nDry run done\n"); exit(0); } } } RunResult Fuzzer::RunSampleAndGetCoverage(ThreadContext *tc, Sample *sample, Coverage *coverage, uint32_t init_timeout, uint32_t timeout) { // from this point on, the sample could be filtered Sample filteredSample; if (OutputFilter(sample, &filteredSample, tc)) { sample = &filteredSample; } // not protected by a mutex but not important to be perfectly accurate total_execs++; if (!tc->sampleDelivery->DeliverSample(sample)) { WARN("Error delivering sample, retrying with a clean target"); tc->instrumentation->CleanTarget(); bool delivery_successful = false; for (int retry = 0; retry < DELIVERY_RETRY_TIMES; retry++) { if (tc->sampleDelivery->DeliverSample(sample)) { WARN("Sample delivery completed successfully after %d retries\n", (retry + 1)); delivery_successful = true; break; } else { WARN("Repeatedly failed to deliver sample, retrying after delay"); #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) Sleep(1000); #else usleep(1000000); #endif } } if (!delivery_successful) { FATAL("Repeatedly failed to deliver sample"); } } RunResult result = tc->instrumentation->Run(tc->target_argc, tc->target_argv, init_timeout, timeout); tc->instrumentation->GetCoverage(*coverage, true); // save crashes and hangs immediately when they are detected if (result == CRASH) { string crash_desc = tc->instrumentation->GetCrashName(); if (crash_reproduce_retries > 0) { if (TryReproduceCrash(tc, sample, init_timeout, timeout) == CRASH) { // get a hopefully better name crash_desc = tc->instrumentation->GetCrashName(); } else { crash_desc = "flaky_" + crash_desc; } } bool should_save_crash = false; int duplicates = 0; crash_mutex.Lock(); num_crashes++; auto crash_it = unique_crashes.find(crash_desc); if(crash_it == unique_crashes.end()) { should_save_crash = true; duplicates = 1; unique_crashes[crash_desc] = 1; num_unique_crashes++; } else { if(crash_it->second < MAX_IDENTICAL_CRASHES) { should_save_crash = true; crash_it->second++; duplicates = crash_it->second; } } crash_mutex.Unlock(); if(should_save_crash) { string crash_filename = crash_desc + "_" + std::to_string(duplicates); output_mutex.Lock(); string outfile = DirJoin(crash_dir, crash_filename); sample->Save(outfile.c_str()); output_mutex.Unlock(); if (server) { server_mutex.Lock(); server->ReportCrash(sample, crash_desc); server_mutex.Unlock(); } } } if (result == HANG) { output_mutex.Lock(); if (save_hangs) { string outfile = DirJoin(hangs_dir, string("hang_") + std::to_string(num_hangs)); sample->Save(outfile.c_str()); } num_hangs++; output_mutex.Unlock(); } return result; } RunResult Fuzzer::TryReproduceCrash(ThreadContext* tc, Sample* sample, uint32_t init_timeout, uint32_t timeout) { RunResult result; for (int i = 0; i < crash_reproduce_retries; i++) { total_execs++; if (!tc->sampleDelivery->DeliverSample(sample)) { WARN("Error delivering sample, retrying with a clean target"); tc->instrumentation->CleanTarget(); if (!tc->sampleDelivery->DeliverSample(sample)) { WARN("Repeatedly failed to deliver sample when reproducing a crash, the crashs will be saved as flaky"); return HANG; } } result = tc->instrumentation->RunWithCrashAnalysis(tc->target_argc, tc->target_argv, init_timeout, timeout); tc->instrumentation->ClearCoverage(); if (result == CRASH) return result; } return result; } void Fuzzer::SaveSample(ThreadContext *tc, Sample *sample, uint32_t init_timeout, uint32_t timeout, Sample *original_sample) { std::vector ranges; if (track_ranges) { // need to rerun the sample as the minimizer could have changed ranges Coverage tmp_coverage; RunResult result = RunSampleAndGetCoverage(tc, sample, &tmp_coverage, init_timeout, timeout); // this could fail, but the chance is it will be OK // If it fails and no ranges are extracted, // we'll just mutate the entire sample if (result == OK) { tc->range_tracker->ExtractRanges(&ranges); } } output_mutex.Lock(); char fileindex[20]; sprintf(fileindex, "%05lld", num_samples); string filename = string("sample_") + fileindex; string outfile = DirJoin(sample_dir, filename); sample->Save(outfile.c_str()); num_samples++; output_mutex.Unlock(); SampleQueueEntry *new_entry = new SampleQueueEntry(); Sample *new_sample = new Sample(*sample); new_entry->sample = new_sample; new_entry->context = tc->mutator->CreateSampleContext(new_entry->sample); if(TrackHotOffsets()) { if (keep_samples_in_memory) { size_t mutation_offset = sample_trie.AddSample(new_sample); tc->mutator->AddHotOffset(new_entry->context, mutation_offset); } else if (original_sample) { size_t mutation_offset = original_sample->FindFirstDiff(*new_sample); tc->mutator->AddHotOffset(new_entry->context, mutation_offset); } } new_entry->priority = 0; new_entry->sample_index = num_samples - 1; new_entry->sample_filename = filename; new_entry->ranges = ranges; if (!keep_samples_in_memory) { new_sample->filename = outfile; new_sample->FreeMemory(); } queue_mutex.Lock(); all_samples.push_back(new_sample); all_entries.push_back(new_entry); sample_queue.push(new_entry); queue_mutex.Unlock(); } RunResult Fuzzer::RunSample(ThreadContext *tc, Sample *sample, int *has_new_coverage, bool trim, bool report_to_server, uint32_t init_timeout, uint32_t timeout, Sample *original_sample) { if (has_new_coverage) { *has_new_coverage = 0; } Coverage initialCoverage; RunResult result = RunSampleAndGetCoverage(tc, sample, &initialCoverage, init_timeout, timeout); if (result != OK) return result; if (!IsReturnValueInteresting(tc->instrumentation->GetReturnValue())) return result; if (initialCoverage.empty()) return result; if(!incremental_coverage) { Coverage new_thread_coverage; CoverageDifference(tc->thread_coverage, initialCoverage, new_thread_coverage); if(new_thread_coverage.empty()) return result; } // printf("found new coverage: \n"); // PrintCoverage(initialCoverage); // the sample returned new coverage Coverage stableCoverage = initialCoverage; Coverage totalCoverage = initialCoverage; // have a clean target before retrying the sample if(clean_target_on_coverage) tc->instrumentation->CleanTarget(); for (int i = 0; i < coverage_reproduce_retries; i++) { Coverage retryCoverage, tmpCoverage; result = RunSampleAndGetCoverage(tc, sample, &retryCoverage, init_timeout, timeout); if (result != OK) return result; // printf("Retry %d, coverage:\n", i); // PrintCoverage(retryCoverage); MergeCoverage(totalCoverage, retryCoverage); CoverageIntersection(stableCoverage, retryCoverage, tmpCoverage); stableCoverage = tmpCoverage; } Coverage variableCoverage; CoverageDifference(stableCoverage, totalCoverage, variableCoverage); // printf("Stable coverage:\n"); // PrintCoverage(stableCoverage); // printf("Variable coverage:\n"); // PrintCoverage(variableCoverage); if (InterestingSample(tc, sample, &stableCoverage, &variableCoverage)) { if (has_new_coverage) { *has_new_coverage = 1; } if (trim && minimize_samples) MinimizeSample(tc, sample, &stableCoverage, init_timeout, timeout); if (server && report_to_server) { server_mutex.Lock(); server->ReportNewCoverage(&stableCoverage, sample); server_mutex.Unlock(); } SaveSample(tc, sample, init_timeout, timeout, original_sample); } if (!variableCoverage.empty() && server && report_to_server) { server_mutex.Lock(); server->ReportNewCoverage(&variableCoverage, NULL); server_mutex.Unlock(); } // printf("Total coverage:\n"); // PrintCoverage(totalCoverage); if(incremental_coverage) { tc->instrumentation->IgnoreCoverage(totalCoverage); } else { MergeCoverage(tc->thread_coverage, totalCoverage); } return result; } void Fuzzer::MinimizeSample(ThreadContext *tc, Sample *sample, Coverage* stable_coverage, uint32_t init_timeout, uint32_t timeout) { Minimizer* minimizer = tc->minimizer; if (!minimizer) return; MinimizerContext* context = minimizer->CreateContext(sample); Sample test_sample = *sample; while (1) { if (!minimizer->MinimizeStep(&test_sample, context)) break; Coverage test_coverage; RunResult result = RunSampleAndGetCoverage(tc, &test_sample, &test_coverage, init_timeout, timeout); if (result != OK) break; if (!IsReturnValueInteresting(tc->instrumentation->GetReturnValue()) || !CoverageContains(test_coverage, *stable_coverage)) { minimizer->ReportFail(&test_sample, context); test_sample = *sample; } else { minimizer->ReportSuccess(&test_sample, context); *sample = test_sample; } } delete context; } int Fuzzer::InterestingSample(ThreadContext *tc, Sample *sample, Coverage *stableCoverage, Coverage *variableCoverage) { coverage_mutex.Lock(); Coverage new_stable_coverage; Coverage new_variable_coverage; CoverageDifference(fuzzer_coverage, *stableCoverage, new_stable_coverage); CoverageDifference(fuzzer_coverage, *variableCoverage, new_variable_coverage); MergeCoverage(fuzzer_coverage, new_stable_coverage); MergeCoverage(fuzzer_coverage, new_variable_coverage); coverage_mutex.Unlock(); // printf("New stable coverage:\n"); // PrintCoverage(new_stable_coverage); // printf("New variable coverage:\n"); // PrintCoverage(new_variable_coverage); *stableCoverage = new_stable_coverage; *variableCoverage = new_variable_coverage; if (!new_stable_coverage.empty()) return 1; return 0; } void Fuzzer::SynchronizeAndGetJob(ThreadContext* tc, FuzzerJob* job) { queue_mutex.Lock(); // handle saving and loading first (if needed). // first thread that enters this function restores state if(state == RESTORE_NEEDED) { RestoreState(tc); state = INPUT_SAMPLE_PROCESSING; } // only save state while fuzzing if(state == FUZZING) { uint64_t cur_time = GetCurTime(); if((cur_time > last_save_time) && (((cur_time - last_save_time) / 1000) > FUZZER_SAVE_INERVAL)) { SaveState(tc); last_save_time = cur_time; } } // after restoring the state // ignore the previously seen (restored) coverage if(!tc->coverage_initialized) { if(incremental_coverage) { coverage_mutex.Lock(); tc->instrumentation->IgnoreCoverage(fuzzer_coverage); coverage_mutex.Unlock(); } tc->coverage_initialized = true; } // sync all_samples_local with all_samples if (all_samples.size() > tc->all_samples_local.size()) { size_t old_size = tc->all_samples_local.size(); tc->all_samples_local.resize(all_samples.size()); for (size_t i = old_size; i < all_samples.size(); i++) { tc->all_samples_local[i] = all_samples[i]; } } // change state if needed if ((state == FUZZING) && server && (GetCurTime() > (last_server_update_time_ms + server_update_interval_ms))) { last_server_update_time_ms = GetCurTime(); server_mutex.Lock(); server->GetUpdates(server_samples, total_execs); server_mutex.Unlock(); state = SERVER_SAMPLE_PROCESSING; } if (state == INPUT_SAMPLE_PROCESSING) { if (input_files.empty() && !samples_pending) { if (server) { if(!skip_initial_server_sync) { server_mutex.Lock(); coverage_mutex.Lock(); server->ReportNewCoverage(&fuzzer_coverage, NULL); coverage_mutex.Unlock(); last_server_update_time_ms = GetCurTime(); server->GetUpdates(server_samples, total_execs); server_mutex.Unlock(); } last_server_update_time_ms = GetCurTime(); state = SERVER_SAMPLE_PROCESSING; } else { state = FUZZING; } } } if (state == SERVER_SAMPLE_PROCESSING) { if (server_samples.empty() && !samples_pending) { state = FUZZING; } } if ((state == FUZZING) && (num_samples == 0)) { if (tc->mutator->CanGenerateSample()) { printf("Sample queue is empty, but the mutatator supports sample generation\n"); printf("Will try to generate initial samples\n"); state = GENERATING_SAMPLES; } else { FATAL("No interesting input files\n"); } } if (state == GENERATING_SAMPLES && (sample_queue.size() >= MIN_SAMPLES_TO_GENERATE) && (!samples_pending)) { state = FUZZING; } // create a job according to the state if (state == FUZZING && !dry_run) { if (sample_queue.empty()) { job->type = WAIT; } else { job->type = FUZZ; job->entry = sample_queue.top(); sample_queue.pop(); } } else if (state == INPUT_SAMPLE_PROCESSING) { if (input_files.empty()) { job->type = WAIT; } else { job->type = PROCESS_SAMPLE; std::string filename = input_files.front(); input_files.pop_front(); printf("Running input sample %s\n", filename.c_str()); job->sample = new Sample(); job->sample->Load(filename.c_str()); if (job->sample->size > Sample::max_size) { WARN("Input sample larger than maximum sample size. Will be trimmed"); job->sample->Trim(Sample::max_size); } samples_pending++; } } else if (state == SERVER_SAMPLE_PROCESSING) { if (server_samples.empty()) { job->type = WAIT; } else { job->type = PROCESS_SAMPLE; job->sample = server_samples.front(); server_samples.pop_front(); samples_pending++; } } else if (state == GENERATING_SAMPLES) { if (sample_queue.size() >= MIN_SAMPLES_TO_GENERATE) { job->type = WAIT; } else { job->type = PROCESS_SAMPLE; job->sample = new Sample(); tc->mutator->GenerateSample(job->sample, tc->prng); samples_pending++; } } else { job->type = WAIT; } queue_mutex.Unlock(); } void Fuzzer::JobDone(FuzzerJob* job) { queue_mutex.Lock(); if (job->type == FUZZ) { if (job->discard_sample) { job->entry->discarded = 1; num_samples_discarded++; } else { sample_queue.push(job->entry); } } else if (job->type == PROCESS_SAMPLE) { delete job->sample; samples_pending--; } queue_mutex.Unlock(); } void Fuzzer::FuzzJob(ThreadContext* tc, FuzzerJob* job) { SampleQueueEntry* entry = job->entry; tc->mutator->InitRound(entry->sample, entry->context); if (track_ranges) tc->mutator->SetRanges(&entry->ranges); printf("Fuzzing sample %05lld\n", entry->sample_index); job->discard_sample = false; entry->sample->EnsureLoaded(); while (1) { Sample mutated_sample = *entry->sample; if (!tc->mutator->Mutate(&mutated_sample, tc->prng, tc->all_samples_local)) break; if (mutated_sample.size > Sample::max_size) { continue; } int has_new_coverage; RunResult result = RunSample(tc, &mutated_sample, &has_new_coverage, true, true, init_timeout, timeout, entry->sample); AdjustSamplePriority(tc, entry, has_new_coverage); tc->mutator->NotifyResult(result, has_new_coverage); entry->num_runs++; if (has_new_coverage) { entry->num_newcoverage++; if(TrackHotOffsets()) { size_t diff_offset = entry->sample->FindFirstDiff(mutated_sample); tc->mutator->AddHotOffset(entry->context, diff_offset); } } if (result == HANG) entry->num_hangs++; if (result == CRASH) entry->num_crashes++; if ((entry->num_hangs > 10) && (entry->num_hangs > (entry->num_runs * acceptable_hang_ratio))) { WARN("Sample %lld produces too many hangs. Discarding\n", entry->sample_index); job->discard_sample = true; break; } if ((entry->num_crashes > 100) && (entry->num_crashes > (entry->num_runs * acceptable_crash_ratio))) { WARN("Sample %lld produces too many crashes. Discarding\n", entry->sample_index); job->discard_sample = true; break; } } if (!keep_samples_in_memory) { entry->sample->FreeMemory(); } } void Fuzzer::ProcessSample(ThreadContext* tc, FuzzerJob* job) { int has_new_coverage = 0; job->sample->EnsureLoaded(); RunResult result = RunSample(tc, job->sample, &has_new_coverage, false, false, init_timeout, corpus_timeout, NULL); if (result == CRASH) { WARN("Input sample resulted in a crash"); } else if (result == HANG) { WARN("Input sample resulted in a hang"); } else if (!has_new_coverage) { if(add_all_inputs) { SaveSample(tc, job->sample, init_timeout, corpus_timeout, NULL); } else if(state != GENERATING_SAMPLES) { WARN("Input sample has no new stable coverage"); } } } void Fuzzer::RunFuzzerThread(ThreadContext *tc) { while (1) { FuzzerJob job; SynchronizeAndGetJob(tc, &job); switch (job.type) { case WAIT: #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) Sleep(1000); #else usleep(1000000); #endif break; case PROCESS_SAMPLE: ProcessSample(tc, &job); break; case FUZZ: FuzzJob(tc, &job); break; default: FATAL("Unknown job type"); break; } JobDone(&job); } } void Fuzzer::DumpCoverage() { std::string out_file = DirJoin(out_dir, "coverage.txt"); WriteCoverage(fuzzer_coverage, out_file.c_str()); } void Fuzzer::SaveState(ThreadContext *tc) { // don't save during input sample processing if(state == INPUT_SAMPLE_PROCESSING) return; output_mutex.Lock(); coverage_mutex.Lock(); std::string out_file = DirJoin(out_dir, std::string("state.dat")); FILE *fp = fopen(out_file.c_str(), "wb"); if (!fp) { FATAL("Error saving state"); } fwrite(&num_samples, sizeof(num_samples), 1, fp); fwrite(&num_samples_discarded, sizeof(num_samples_discarded), 1, fp); fwrite(&total_execs, sizeof(total_execs), 1, fp); WriteCoverageBinary(fuzzer_coverage, fp); tc->mutator->SaveGlobalState(fp); uint64_t num_entries = all_entries.size(); fwrite(&num_entries, sizeof(num_entries), 1, fp); for(SampleQueueEntry *entry : all_entries) { entry->Save(fp); tc->mutator->SaveContext(entry->context, fp); } if (server) server->SaveState(fp); // let every correctly saved state end with // hex('fuzzstat'); uint64_t sentry = 0x66757a7a73746174; fwrite(&sentry, sizeof(sentry), 1, fp); fclose(fp); if(dump_coverage) DumpCoverage(); coverage_mutex.Unlock(); output_mutex.Unlock(); } void Fuzzer::RestoreState(ThreadContext *tc) { output_mutex.Lock(); coverage_mutex.Lock(); std::string out_file = DirJoin(out_dir, std::string("state.dat")); FILE *fp = fopen(out_file.c_str(), "rb"); if (!fp) { FATAL("Error restoring state. Did the previous session run long enough for state to be saved?"); } fread(&num_samples, sizeof(num_samples), 1, fp); fread(&num_samples_discarded, sizeof(num_samples_discarded), 1, fp); fread(&total_execs, sizeof(total_execs), 1, fp); ReadCoverageBinary(fuzzer_coverage, fp); tc->mutator->LoadGlobalState(fp); uint64_t num_entries; fread(&num_entries, sizeof(num_entries), 1, fp); for (uint64_t i = 0; i < num_entries; i++) { Sample *sample = new Sample(); SampleQueueEntry *entry = new SampleQueueEntry; entry->Load(fp); string outfile = DirJoin(sample_dir, entry->sample_filename); sample->Load(outfile.c_str()); entry->sample = sample; entry->context = tc->mutator->CreateSampleContext(sample); tc->mutator->LoadContext(entry->context, fp); if (TrackHotOffsets()) { if (keep_samples_in_memory) { sample_trie.AddSample(entry->sample); } } if (!keep_samples_in_memory) { entry->sample->filename = outfile; entry->sample->FreeMemory(); } all_samples.push_back(sample); all_entries.push_back(entry); if(!entry->discarded) sample_queue.push(entry); } if (server) server->LoadState(fp); uint64_t sentry; fread(&sentry, sizeof(sentry), 1, fp); if (sentry != 0x66757a7a73746174) { FATAL("State could not be restored correctly"); } fclose(fp); coverage_mutex.Unlock(); output_mutex.Unlock(); } void Fuzzer::AdjustSamplePriority(ThreadContext *tc, SampleQueueEntry *entry, int found_new_coverage) { if (found_new_coverage) entry->priority = 0; else entry->priority--; } Fuzzer::ThreadContext *Fuzzer::CreateThreadContext(int argc, char **argv, int thread_id) { ThreadContext *tc = new ThreadContext(); // copy arguments for each thread tc->target_argc = target_argc; tc->target_argv = (char **)malloc((target_argc + 1) * sizeof(char *)); for (int i = 0; i < target_argc; i++) { tc->target_argv[i] = target_argv[i]; } tc->target_argv[target_argc] = NULL; tc->thread_id = thread_id; tc->fuzzer = this; tc->prng = CreatePRNG(argc, argv, tc); tc->mutator = CreateMutator(argc, argv, tc); tc->instrumentation = CreateInstrumentation(argc, argv, tc); tc->sampleDelivery = CreateSampleDelivery(argc, argv, tc); tc->minimizer = CreateMinimizer(argc, argv, tc); tc->range_tracker = CreateRangeTracker(argc, argv, tc); tc->coverage_initialized = false; return tc; } bool Fuzzer::MagicOutputFilter(Sample *original_sample, Sample *output_sample, const char *magic, size_t magic_size) { if((original_sample->size >= magic_size) && !memcmp(original_sample->bytes, magic, magic_size)) { return false; } *output_sample = *original_sample; for(int i = 0; i < magic_size; i++) { if(i >= output_sample->size) break; output_sample->bytes[i] = magic[i]; } return true; } void Fuzzer::ReplaceTargetCmdArg(ThreadContext *tc, const std::string &search, const std::string &replace) { for (int i = 0; i < tc->target_argc; i++) { std::string arg(tc->target_argv[i]); size_t pos = 0; bool arg_modified = false; while((pos = arg.find(search, pos)) != std::string::npos) { arg.replace(pos, search.length(), replace); pos += replace.length(); arg_modified = true; } if(arg_modified) { char* arg_buf = (char*)malloc(arg.length() + 1); strcpy(arg_buf, arg.c_str()); tc->target_argv[i] = arg_buf; } } } PRNG *Fuzzer::CreatePRNG(int argc, char **argv, ThreadContext *tc) { return new MTPRNG(); } Instrumentation *Fuzzer::CreateInstrumentation(int argc, char **argv, ThreadContext *tc) { Instrumentation *instrumentation = NULL; #if defined(linux) && !defined(__ANDROID__) char *option = GetOption("-instrumentation", argc, argv); if(option && !strcmp(option, "sancov")) { instrumentation = new SanCovInstrumentation(tc->thread_id); } #endif if(!instrumentation) { instrumentation = new TinyInstInstrumentation(); } instrumentation->Init(argc, argv); return instrumentation; } SampleDelivery *Fuzzer::CreateSampleDelivery(int argc, char **argv, ThreadContext *tc) { char *option = GetOption("-delivery", argc, argv); if (!option || !strcmp(option, "file")) { string extension = ""; char *extension_opt = GetOption("-file_extension", argc, argv); if(extension_opt) { extension = string(".") + string(extension_opt); } string outfile = DirJoin(delivery_dir, string("input_") + std::to_string(tc->thread_id) + extension); ReplaceTargetCmdArg(tc, "@@", outfile); FileSampleDelivery* sampleDelivery = new FileSampleDelivery(); sampleDelivery->Init(argc, argv); sampleDelivery->SetFilename(outfile); return sampleDelivery; } else if (!strcmp(option, "shmem")) { #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) string shm_name = string("shm_fuzz_") + std::to_string(GetCurrentProcessId()) + "_" + std::to_string(tc->thread_id); #else string shm_name = string("/shm_fuzz_") + std::to_string(getpid()) + "_" + std::to_string(tc->thread_id); #endif ReplaceTargetCmdArg(tc, "@@", shm_name); SHMSampleDelivery* sampleDelivery = new SHMSampleDelivery((char*)shm_name.c_str(), Sample::max_size + 4); sampleDelivery->Init(argc, argv); return sampleDelivery; } else { FATAL("Unknown sample delivery option"); } } RangeTracker* Fuzzer::CreateRangeTracker(int argc, char** argv, ThreadContext* tc) { if (!track_ranges) { return new RangeTracker(); } else { #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) string shm_name = string("shm_ranges_") + std::to_string(GetCurrentProcessId()) + "_" + std::to_string(tc->thread_id); #else string shm_name = string("/shm_ranges_") + std::to_string(getpid()) + "_" + std::to_string(tc->thread_id); #endif ReplaceTargetCmdArg(tc, "@ranges@", shm_name); return new SHMRangeTracker((char*)shm_name.c_str(), RANGE_SHM_SIZE); } } Minimizer* Fuzzer::CreateMinimizer(int argc, char** argv, ThreadContext* tc) { SimpleTrimmer* trimmer = new SimpleTrimmer(); return trimmer; } bool Fuzzer::OutputFilter(Sample *original_sample, Sample *output_sample, ThreadContext* tc) { // use the original sample return false; } void Fuzzer::SampleQueueEntry::Save(FILE *fp) { uint64_t filename_size = sample_filename.size(); fwrite(&filename_size, sizeof(filename_size), 1, fp); fwrite(sample_filename.data(), filename_size, 1, fp); fwrite(&priority, sizeof(priority), 1, fp); fwrite(&sample_index, sizeof(sample_index), 1, fp); fwrite(&num_runs, sizeof(num_runs), 1, fp); fwrite(&num_crashes, sizeof(num_crashes), 1, fp); fwrite(&num_hangs, sizeof(num_hangs), 1, fp); fwrite(&num_newcoverage, sizeof(num_newcoverage), 1, fp); fwrite(&discarded, sizeof(discarded), 1, fp); uint64_t ranges_size = ranges.size(); fwrite(&ranges_size, sizeof(ranges_size), 1, fp); fwrite(ranges.data(), sizeof(ranges[0]), ranges_size, fp); } void Fuzzer::SampleQueueEntry::Load(FILE *fp) { uint64_t filename_size; fread(&filename_size, sizeof(filename_size), 1, fp); char *str_buf = (char *)malloc(filename_size + 1); fread(str_buf, filename_size, 1, fp); str_buf[filename_size] = 0; sample_filename = str_buf; free(str_buf); fread(&priority, sizeof(priority), 1, fp); fread(&sample_index, sizeof(sample_index), 1, fp); fread(&num_runs, sizeof(num_runs), 1, fp); fread(&num_crashes, sizeof(num_crashes), 1, fp); fread(&num_hangs, sizeof(num_hangs), 1, fp); fread(&num_newcoverage, sizeof(num_newcoverage), 1, fp); fread(&discarded, sizeof(discarded), 1, fp); uint64_t ranges_size; fread(&ranges_size, sizeof(ranges_size), 1, fp); ranges.resize(ranges_size); fread(ranges.data(), sizeof(ranges[0]), ranges_size, fp); }