| // Copyright 2022 The Centipede Authors. | |
| // | |
| // 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. | |
| #include "./centipede/minimize_crash.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdlib> | |
| #include <filesystem> // NOLINT | |
| #include <string> | |
| #include <string_view> | |
| #include <thread> // NOLINT(build/c++11) | |
| #include <vector> | |
| #include "absl/base/thread_annotations.h" | |
| #include "absl/log/check.h" | |
| #include "absl/log/log.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include "./centipede/centipede_callbacks.h" | |
| #include "./centipede/defs.h" | |
| #include "./centipede/early_exit.h" | |
| #include "./centipede/environment.h" | |
| #include "./centipede/logging.h" // IWYU pragma: keep | |
| #include "./centipede/mutation_input.h" | |
| #include "./centipede/runner_result.h" | |
| #include "./centipede/util.h" | |
| #include "./centipede/workdir.h" | |
| namespace centipede { | |
| // Work queue for the minimizer. | |
| // Thread-safe. | |
| struct MinimizerWorkQueue { | |
| public: | |
| // Creates the queue. | |
| // `crash_dir_path` is the directory path where new crashers are written. | |
| // `crasher` is the initial crashy input. | |
| MinimizerWorkQueue(const std::string_view crash_dir_path, | |
| const ByteArray crasher) | |
| : crash_dir_path_(crash_dir_path), crashers_{ByteArray(crasher)} { | |
| std::filesystem::create_directory(crash_dir_path_); | |
| } | |
| // Returns up to `max_num_crashers` most recently added crashers. | |
| std::vector<ByteArray> GetRecentCrashers(size_t max_num_crashers) { | |
| absl::MutexLock lock(&mutex_); | |
| size_t num_crashers_to_return = | |
| std::min(crashers_.size(), max_num_crashers); | |
| return {crashers_.end() - num_crashers_to_return, crashers_.end()}; | |
| } | |
| // Adds `crasher` to the queue, writes it to `crash_dir_path_/Hash(crasher)`. | |
| // The crasher must be smaller than the original one. | |
| void AddCrasher(ByteArray crasher) { | |
| absl::MutexLock lock(&mutex_); | |
| CHECK_LT(crasher.size(), crashers_.front().size()); | |
| crashers_.emplace_back(crasher); | |
| // Write the crasher to disk. | |
| auto hash = Hash(crasher); | |
| auto dir = crash_dir_path_; | |
| std::string file_path = dir.append(hash); | |
| WriteToLocalFile(file_path, crasher); | |
| } | |
| // Returns true if new smaller crashes were found. | |
| bool SmallerCrashesFound() const { | |
| absl::MutexLock lock(&mutex_); | |
| return crashers_.size() > 1; | |
| } | |
| private: | |
| mutable absl::Mutex mutex_; | |
| const std::filesystem::path crash_dir_path_; | |
| std::vector<ByteArray> crashers_ ABSL_GUARDED_BY(mutex_); | |
| }; | |
| // Performs a minimization loop in one thread. | |
| static void MinimizeCrash(const Environment &env, | |
| CentipedeCallbacksFactory &callbacks_factory, | |
| MinimizerWorkQueue &queue) { | |
| ScopedCentipedeCallbacks scoped_callback(callbacks_factory, env); | |
| auto callbacks = scoped_callback.callbacks(); | |
| BatchResult batch_result; | |
| size_t num_batches = env.num_runs / env.batch_size; | |
| for (size_t i = 0; i < num_batches; ++i) { | |
| LOG_EVERY_POW_2(INFO) << "[" << i << "] Minimizing... Interrupt to stop"; | |
| if (EarlyExitRequested()) break; | |
| // Get up to kMaxNumCrashersToGet most recent crashers. We don't want just | |
| // the most recent crasher to avoid being stuck in local minimum. | |
| constexpr size_t kMaxNumCrashersToGet = 20; | |
| const auto recent_crashers = queue.GetRecentCrashers(kMaxNumCrashersToGet); | |
| CHECK(!recent_crashers.empty()); | |
| // Compute the minimal known crasher size. | |
| size_t min_known_size = recent_crashers.front().size(); | |
| for (const auto &crasher : recent_crashers) { | |
| min_known_size = std::min(min_known_size, crasher.size()); | |
| } | |
| // Create several mutants that are smaller than the current smallest one. | |
| // | |
| // Currently, we do this by calling the vanilla mutator and | |
| // discarding all inputs that are too large. | |
| // TODO(kcc): modify the Mutate() interface such that max_len can be passed. | |
| // | |
| std::vector<ByteArray> mutants; | |
| callbacks->Mutate(GetMutationInputRefsFromDataInputs(recent_crashers), | |
| env.batch_size, mutants); | |
| std::vector<ByteArray> smaller_mutants; | |
| for (const auto &m : mutants) { | |
| if (m.size() < min_known_size) smaller_mutants.push_back(m); | |
| } | |
| // Execute all mutants. If a new crasher is found, add it to `queue`. | |
| if (!callbacks->Execute(env.binary, smaller_mutants, batch_result)) { | |
| size_t crash_inputs_idx = batch_result.num_outputs_read(); | |
| CHECK_LT(crash_inputs_idx, smaller_mutants.size()); | |
| const auto &new_crasher = smaller_mutants[crash_inputs_idx]; | |
| LOG(INFO) << "Crasher: size: " << new_crasher.size() << ": " | |
| << AsPrintableString(new_crasher, /*max_len=*/40); | |
| queue.AddCrasher(new_crasher); | |
| } | |
| } | |
| } | |
| int MinimizeCrash(ByteSpan crashy_input, const Environment &env, | |
| CentipedeCallbacksFactory &callbacks_factory) { | |
| ScopedCentipedeCallbacks scoped_callback(callbacks_factory, env); | |
| auto callbacks = scoped_callback.callbacks(); | |
| LOG(INFO) << "MinimizeCrash: trying the original crashy input"; | |
| BatchResult batch_result; | |
| ByteArray original_crashy_input(crashy_input.begin(), crashy_input.end()); | |
| if (callbacks->Execute(env.binary, {original_crashy_input}, batch_result)) { | |
| LOG(INFO) << "The original crashy input did not crash; exiting"; | |
| return EXIT_FAILURE; | |
| } | |
| LOG(INFO) << "Starting the crash minimization loop in " << env.num_threads | |
| << "threads"; | |
| MinimizerWorkQueue queue(WorkDir{env}.CrashReproducerDirPath(), | |
| original_crashy_input); | |
| std::vector<std::thread> threads(env.num_threads); | |
| for (auto &thread : threads) { | |
| thread = | |
| std::thread([&]() { MinimizeCrash(env, callbacks_factory, queue); }); | |
| } | |
| for (auto &thread : threads) { | |
| thread.join(); | |
| } | |
| return queue.SmallerCrashesFound() ? EXIT_SUCCESS : EXIT_FAILURE; | |
| } | |
| } // namespace centipede |