Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions centipede/binary_info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ void BinaryInfo::InitializeFromSanCovBinary(
std::filesystem::path{tmp_dir_path} / "binary_info_log_tmp";
FUZZTEST_LOG(INFO) << __func__ << ": tmp_dir: " << tmp_dir;

env_diff.push_back(absl::StrCat(
"CENTIPEDE_RUNNER_FLAGS=:dump_binary_info:arg1=", pc_table_path.path(),
":arg2=", cf_table_path.path(), ":arg3=", dso_table_path.path(), ":"));
env_diff.push_back(
absl::StrCat("CENTIPEDE_RUNNER_FLAGS=:dump_binary_info:arg1=",
EngineFlagEscape(pc_table_path.path()),
":arg2=", EngineFlagEscape(cf_table_path.path()),
":arg3=", EngineFlagEscape(dso_table_path.path()), ":"));
Command::Options cmd_options;
cmd_options.env_diff = std::move(env_diff);
cmd_options.stdout_file_prefix = log_prefix;
Expand Down
38 changes: 22 additions & 16 deletions centipede/centipede_callbacks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -360,11 +360,12 @@ std::string CentipedeCallbacks::ConstructRunnerFlags(
if (env_.use_dataflow_features) flags.emplace_back("use_dataflow_features");
}
if (!env_.runner_dl_path_suffix.empty()) {
flags.emplace_back(
absl::StrCat("dl_path_suffix=", env_.runner_dl_path_suffix));
flags.emplace_back(absl::StrCat(
"dl_path_suffix=", EngineFlagEscape(env_.runner_dl_path_suffix)));
}
if (!env_.pcs_file_path.empty())
flags.emplace_back(absl::StrCat("pcs_file_path=", env_.pcs_file_path));
flags.emplace_back(
absl::StrCat("pcs_file_path=", EngineFlagEscape(env_.pcs_file_path)));
if (!extra_flags.empty()) flags.emplace_back(extra_flags);
flags.emplace_back("");
return absl::StrJoin(flags, ":");
Expand Down Expand Up @@ -399,15 +400,20 @@ CentipedeCallbacks::GetOrCreateCommandContextForBinary(
}
std::vector<std::string> env_diff = env_.env_diff_for_binaries;
env_diff.push_back(ConstructRunnerFlags(
absl::StrCat(":shmem:test=", env_.test_name, ":arg1=",
inputs_blobseq_.path(), ":arg2=", outputs_blobseq_.path(),
":failure_description_path=", failure_description_path_,
":failure_signature_path=", failure_signature_path_,
persistent_mode_server == nullptr
? ""
: absl::StrCat(":persistent_mode_socket=",
persistent_mode_server->server_path()),
":"),
absl::StrCat(
":shmem_size_mb=", env_.shmem_size_mb,
":test=", EngineFlagEscape(env_.test_name),
":arg1=", EngineFlagEscape(inputs_blobseq_.path()),
":arg2=", EngineFlagEscape(outputs_blobseq_.path()),
":failure_description_path=",
EngineFlagEscape(failure_description_path_),
":failure_signature_path=", EngineFlagEscape(failure_signature_path_),
persistent_mode_server == nullptr
? ""
: absl::StrCat(
":persistent_mode_socket=",
EngineFlagEscape(persistent_mode_server->server_path())),
":"),
disable_coverage));

if (env_.clang_coverage_binary == binary) {
Expand Down Expand Up @@ -650,7 +656,7 @@ bool CentipedeCallbacks::GetSeedsViaExternalBinary(

std::string centipede_runner_flags = absl::StrCat(
"CENTIPEDE_RUNNER_FLAGS=:dump_seed_inputs:test=", env_.test_name,
":arg1=", output_dir.string(), ":");
":arg1=", EngineFlagEscape(output_dir.string()), ":");
if (!env_.runner_dl_path_suffix.empty()) {
absl::StrAppend(&centipede_runner_flags,
"dl_path_suffix=", env_.runner_dl_path_suffix, ":");
Expand Down Expand Up @@ -716,10 +722,10 @@ bool CentipedeCallbacks::GetSerializedTargetConfigViaExternalBinary(
std::filesystem::path{temp_dir_} / "configuration";
std::string centipede_runner_flags =
absl::StrCat("CENTIPEDE_RUNNER_FLAGS=:dump_configuration:arg1=",
config_file_path.string(), ":");
EngineFlagEscape(config_file_path.string()), ":");
if (!env_.runner_dl_path_suffix.empty()) {
absl::StrAppend(&centipede_runner_flags,
"dl_path_suffix=", env_.runner_dl_path_suffix, ":");
absl::StrAppend(&centipede_runner_flags, "dl_path_suffix=",
EngineFlagEscape(env_.runner_dl_path_suffix), ":");
}
Command::Options cmd_options;
cmd_options.env_diff = env_.env_diff_for_binaries;
Expand Down
37 changes: 32 additions & 5 deletions centipede/dispatcher_flag_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,39 @@ struct DispatcherFlagHelper {
const char *absl_nullable GetStringFlag(const char *absl_nonnull flag) const {
if (!flags) return nullptr;
// Extract "value" from ":flag=value:" inside centipede_runner_flags.
const char *beg = strstr(flags, flag);
const size_t flag_len = strlen(flag);
const char* beg = flags;
while (true) {
beg = strstr(beg, flag);
if (beg == nullptr) return nullptr;
// Find the closest non-backslash before the candiate position.
const char* cur = beg - 1;
while (cur >= flags && *cur == '\\') --cur;
// There are even number of backslashes before the flag, meaning it's not
// escaped.
if (((beg - cur) & 1) == 1) break;
beg = beg + flag_len;
}
if (!beg) return nullptr;
const char *value_beg = beg + strlen(flag);
const char *end = strstr(value_beg, ":");
if (!end) return nullptr;
return strndup(value_beg, end - value_beg);
const char* value_beg = beg + flag_len;
const char* end = value_beg;
while (*end) {
if (*end == '\\' && *(end + 1) != 0) {
end += 2;
continue;
}
if (*end == ':') break;
++end;
}
if (!end || !*end) return nullptr;
char* r = strndup(value_beg, end - value_beg);
size_t d = 0;
for (const char* c = r; *c != 0; ++c) {
if (*c == '\\' && *(c + 1) != 0) ++c;
r[d++] = *c;
}
r[d] = 0;
return r;
}
};

Expand Down
51 changes: 38 additions & 13 deletions centipede/engine_worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ struct WorkerFlags {
// allocates memory (enforced by `WorkerInitEarly`). After that it would be
// signal-safe.
//
// The worker flags format is `:(NAME=VALUE|SWITCH:)+`. `GetWorkerFlags`
// The worker flags format is `:(NAME=VALUE:|SWITCH:)+`. `GetWorkerFlags`
// replaces `:` with '\0' so that we can get null-terminated strings of VALUE
// without copying them, which is important for signal-safety.
const WorkerFlags& GetWorkerFlags() {
Expand All @@ -127,15 +127,23 @@ const WorkerFlags& GetWorkerFlags() {
WorkerLog("Cannot allocate the worker flags", LogLnSync{});
std::_Exit(1);
}
memcpy(str, env_flags, len);
str[len] = 0;
WorkerLog("Got worker flags ", std::string_view{str, len}, LogLnSync{});
// Post-processing to make '\0' as the separator, making each item as a
// null-terminating string to be used without copying it.
for (size_t i = 0; i < len; ++i) {
if (str[i] == ':') str[i] = 0;
size_t src = 0;
size_t dst = 0;
while (src < len) {
if (env_flags[src] == ':') {
str[dst] = '\0';
} else {
if (env_flags[src] == '\\' && src + 1 < len) {
++src;
}
str[dst] = env_flags[src];
}
++src;
++dst;
}
return WorkerFlags{true, len, str};
str[dst] = 0;
WorkerLog("Got worker flags ", std::string_view{str, dst}, LogLnSync{});
return WorkerFlags{true, dst, str};
}();
return worker_flags;
}
Expand Down Expand Up @@ -241,6 +249,7 @@ constexpr std::string_view kWorkerPersistentModeSocketPathFlagHeader =
"persistent_mode_socket="; // TODO: Use better flag names when
// standardizing the protocol.
constexpr std::string_view kWorkerCrossOverLevel = "crossover_level=";
constexpr std::string_view kWorkerShmemSizeMbFlagHeader = "shmem_size_mb=";

struct WorkerState {
std::atomic<bool> has_failure_output = false;
Expand Down Expand Up @@ -407,28 +416,44 @@ __attribute__((constructor(200))) void WorkerInitEarly() {
LogLnSync{});
}

size_t GetShmemSize() {
static auto result = []() -> size_t {
const char* shmem_size_mb_str = GetWorkerFlag(kWorkerShmemSizeMbFlagHeader);
if (shmem_size_mb_str != nullptr) {
const int parsed =
atoi(shmem_size_mb_str); // NOLINT: can't use strto64, etc.
if (parsed < 0) return 0;
return static_cast<size_t>(parsed) << 20;
}
return 0;
}();
return result;
}

BlobSequence* GetInputsBlobSequence() {
static auto result = []() -> BlobSequence* {
if (!HasWorkerSwitchFlag("shmem")) {
const size_t shmem_size = GetShmemSize();
if (shmem_size == 0) {
return nullptr;
}
const char* input_path =
GetWorkerFlag(kWorkerInputsBlobSequencePathFlagHeader);
WorkerCheck(input_path != nullptr, "inputs blob sequence is missing");
return new SharedMemoryBlobSequence(input_path);
return new SharedMemoryBlobSequence(input_path, shmem_size);
}();
return result;
}

BlobSequence* GetOutputsBlobSequence() {
static auto result = []() -> BlobSequence* {
if (!HasWorkerSwitchFlag("shmem")) {
const size_t shmem_size = GetShmemSize();
if (shmem_size == 0) {
return nullptr;
}
const char* output_path =
GetWorkerFlag(kWorkerOutputsBlobSequencePathFlagHeader);
WorkerCheck(output_path != nullptr, "outputs blob sequence is missing");
return new SharedMemoryBlobSequence(output_path);
return new SharedMemoryBlobSequence(output_path, shmem_size);
}();
return result;
}
Expand Down
2 changes: 2 additions & 0 deletions centipede/minimize_crash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ void MinimizeCrash(ByteSpan crashy_input, const Environment& env,
auto callbacks = scoped_callback.callbacks();

FUZZTEST_LOG(INFO) << "MinimizeCrash: trying the original crashy input";
CreateLocalDirRemovedAtExit(TemporaryLocalDirPath());

BatchResult batch_result;
ByteArray original_crashy_input(crashy_input.begin(), crashy_input.end());
Expand All @@ -163,6 +164,7 @@ void MinimizeCrash(ByteSpan crashy_input, const Environment& env,
ThreadPool threads{static_cast<int>(env.num_threads)};
for (size_t i = 0; i < env.num_threads; ++i) {
threads.Schedule([&env, &callbacks_factory, &queue, &stop_condition]() {
CreateLocalDirRemovedAtExit(TemporaryLocalDirPath());
MinimizeCrash(env, callbacks_factory, queue, stop_condition);
});
}
Expand Down
19 changes: 11 additions & 8 deletions centipede/runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -902,9 +902,10 @@ void GlobalRunnerState::OnTermination() {
// This means, the binary is standalone with its own main(), and we need to
// report the coverage now.
if (!state->centipede_runner_main_executed &&
flag_helper.HasFlag(":shmem:")) {
sancov_state->shmem_size_mb != 0) {
PostProcessSancov(); // TODO(xinhaoyuan): do we know our exit status?
SharedMemoryBlobSequence outputs_blobseq(sancov_state->arg2);
SharedMemoryBlobSequence outputs_blobseq(sancov_state->arg2,
sancov_state->shmem_size_mb << 20);
StartSendingOutputsToEngine(outputs_blobseq);
FinishSendingOutputsToEngine(outputs_blobseq);
}
Expand Down Expand Up @@ -987,9 +988,9 @@ static int HandlePersistentMode(RunnerCallbacks& callbacks,
return EXIT_SUCCESS;
}

// If HasFlag(:shmem:), state->arg1 and state->arg2 are the names
// of in/out shared memory locations.
// Read inputs and write outputs via shared memory.
// If sancov_state->shmem_size_mb is non-zero, state->arg1 and state->arg2 are
// the names of in/out shared memory locations.
// Read inputs and write outputs via shared memory.
//
// Default: Execute ReadOneInputExecuteItAndDumpCoverage() for all inputs.//
//
Expand All @@ -1013,10 +1014,12 @@ int RunnerMain(int argc, char** argv, RunnerCallbacks& callbacks) {
}

// Inputs / outputs from shmem.
if (state->flag_helper.HasFlag(":shmem:")) {
if (sancov_state->shmem_size_mb != 0) {
if (!sancov_state->arg1 || !sancov_state->arg2) return EXIT_FAILURE;
SharedMemoryBlobSequence inputs_blobseq(sancov_state->arg1);
SharedMemoryBlobSequence outputs_blobseq(sancov_state->arg2);
SharedMemoryBlobSequence inputs_blobseq(sancov_state->arg1,
sancov_state->shmem_size_mb << 20);
SharedMemoryBlobSequence outputs_blobseq(sancov_state->arg2,
sancov_state->shmem_size_mb << 20);
// Persistent mode loop.
if (state->persistent_mode_socket > 0) {
return HandlePersistentMode(callbacks, inputs_blobseq, outputs_blobseq);
Expand Down
2 changes: 2 additions & 0 deletions centipede/sancov_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ struct SancovState {
const char *arg1 = flag_helper.GetStringFlag(":arg1=");
const char *arg2 = flag_helper.GetStringFlag(":arg2=");
const char *arg3 = flag_helper.GetStringFlag(":arg3=");
const size_t shmem_size_mb =
static_cast<size_t>(flag_helper.HasIntFlag(":shmem_size_mb=", 0));

SancovFlags flags = {
/*path_level=*/std::min(ThreadLocalSancovState::kBoundedPathLength,
Expand Down
8 changes: 4 additions & 4 deletions centipede/shared_memory_blob_sequence.cc
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *name,
MmapData();
}

SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *path) {
SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char* path,
size_t size) {
ErrorOnFailure(size < sizeof(Blob::size), "Size too small");
size_ = size;
// This is a quick way to tell shm-allocated paths from memfd paths without
// requiring the caller to specify.
if (strncmp(path, "/proc/", 6) == 0) {
Expand All @@ -146,9 +149,6 @@ SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *path) {
ErrorOnFailure(fd_ < 0, "open() failed");
strncpy(path_, path, PATH_MAX);
ErrorOnFailure(path_[PATH_MAX - 1] != 0, "path length exceeds PATH_MAX.");
struct stat statbuf = {};
ErrorOnFailure(fstat(fd_, &statbuf), "fstat() failed");
size_ = statbuf.st_size;
MmapData();
}

Expand Down
6 changes: 3 additions & 3 deletions centipede/shared_memory_blob_sequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class BlobSequence {
//
// void Child() {
// // Open an existing blob sequence.
// SharedMemoryBlobSequence child("/foo");
// SharedMemoryBlobSequence child("/foo", 1000);
//
// // Read the data written by parent.
// while (true) {
Expand All @@ -155,9 +155,9 @@ class SharedMemoryBlobSequence : public BlobSequence {
// memfd_create(2).
SharedMemoryBlobSequence(const char *name, size_t size, bool use_posix_shmem);

// Opens an existing shared blob sequence with the file `path`.
// Opens an existing shared blob sequence with the file `path` and `size`.
// Aborts on any failure.
explicit SharedMemoryBlobSequence(const char *path);
SharedMemoryBlobSequence(const char* path, size_t size);

// Releases all resources.
~SharedMemoryBlobSequence();
Expand Down
6 changes: 3 additions & 3 deletions centipede/shared_memory_blob_sequence_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ TEST_P(SharedMemoryBlobSequenceTest, ParentChild) {
EXPECT_TRUE(parent.Write(BlobFromVec(kTestData2, 456)));

// Child created.
SharedMemoryBlobSequence child(parent.path());
SharedMemoryBlobSequence child(parent.path(), 1000);
// Child reads data.
auto blob1 = child.Read();
EXPECT_EQ(kTestData1, Vec(blob1));
Expand Down Expand Up @@ -141,14 +141,14 @@ TEST_P(SharedMemoryBlobSequenceTest, CheckForResourceLeaks) {
for (int iter = 0; iter < kNumIters; iter++) {
SharedMemoryBlobSequence parent(ShmemName().c_str(), kBlobSize, GetParam());
parent.Write(BlobFromVec({1, 2, 3}));
SharedMemoryBlobSequence child(parent.path());
SharedMemoryBlobSequence child(parent.path(), kBlobSize);
EXPECT_EQ(child.Read().size, 3);
}
// Create a parent blob, then create and destroy lots of child blobs.
SharedMemoryBlobSequence parent(ShmemName().c_str(), kBlobSize, GetParam());
parent.Write(BlobFromVec({1, 2, 3, 4}));
for (int iter = 0; iter < kNumIters; iter++) {
SharedMemoryBlobSequence child(parent.path());
SharedMemoryBlobSequence child(parent.path(), kBlobSize);
EXPECT_EQ(child.Read().size, 4);
}
}
Expand Down
7 changes: 7 additions & 0 deletions centipede/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,11 @@ int PollTimeoutMs(absl::Duration timeout) {
return static_cast<int>(ms);
}

std::string EngineFlagEscape(std::string_view value) {
return absl::StrReplaceAll(value, {
{":", "\\:"},
{"\\", "\\\\"},
});
}

} // namespace fuzztest::internal
3 changes: 3 additions & 0 deletions centipede/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ class MmapNoReserveArray {
// Converts `timeout` to an integer value of milliseconds suitable for `poll()`.
int PollTimeoutMs(absl::Duration timeout);

// Returns properly escaped `value` to be part of an engine flag.
std::string EngineFlagEscape(std::string_view value);

} // namespace fuzztest::internal

#endif // THIRD_PARTY_CENTIPEDE_UTIL_H_
Loading