forked from google/centipede
-
Notifications
You must be signed in to change notification settings - Fork 5
/
minimize_crash.cc
156 lines (135 loc) · 5.72 KB
/
minimize_crash.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// 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 "./minimize_crash.h"
#include <algorithm>
#include <filesystem> // NOLINT
#include <thread> // NOLINT(build/c++11)
#include "absl/synchronization/mutex.h"
#include "./centipede_callbacks.h"
#include "./defs.h"
#include "./environment.h"
#include "./logging.h"
#include "./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(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