blob: 62ce2804ee514dc0d9b4c1686edae6e07d309549 [file] [log] [blame]
// 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 <filesystem> // NOLINT
#include <thread> // NOLINT(build/c++11)
#include "absl/synchronization/mutex.h"
#include "./centipede/centipede_callbacks.h"
#include "./centipede/defs.h"
#include "./centipede/environment.h"
#include "./centipede/logging.h"
#include "./centipede/util.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() << ": "
<< AsString(new_crasher, 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(env.MakeCrashReproducerDirPath(),
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