From 103824afe27c969b6c18fbde799407d6d375fa1e Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Thu, 6 Aug 2026 09:48:26 -0700 Subject: [PATCH] No public description PiperOrigin-RevId: 960357788 --- centipede/binary_info.cc | 8 +-- centipede/centipede_callbacks.cc | 38 ++++++++------ centipede/dispatcher_flag_helper.h | 37 ++++++++++++-- centipede/engine_worker.cc | 51 ++++++++++++++----- centipede/minimize_crash.cc | 2 + centipede/runner.cc | 19 ++++--- centipede/sancov_state.h | 2 + centipede/shared_memory_blob_sequence.cc | 8 +-- centipede/shared_memory_blob_sequence.h | 6 +-- centipede/shared_memory_blob_sequence_test.cc | 6 +-- centipede/util.cc | 7 +++ centipede/util.h | 3 ++ 12 files changed, 132 insertions(+), 55 deletions(-) diff --git a/centipede/binary_info.cc b/centipede/binary_info.cc index 0d02c53eb..aba3652ce 100644 --- a/centipede/binary_info.cc +++ b/centipede/binary_info.cc @@ -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; diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc index 961e93535..742926c7b 100644 --- a/centipede/centipede_callbacks.cc +++ b/centipede/centipede_callbacks.cc @@ -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, ":"); @@ -399,15 +400,20 @@ CentipedeCallbacks::GetOrCreateCommandContextForBinary( } std::vector 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) { @@ -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(¢ipede_runner_flags, "dl_path_suffix=", env_.runner_dl_path_suffix, ":"); @@ -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(¢ipede_runner_flags, - "dl_path_suffix=", env_.runner_dl_path_suffix, ":"); + absl::StrAppend(¢ipede_runner_flags, "dl_path_suffix=", + EngineFlagEscape(env_.runner_dl_path_suffix), ":"); } Command::Options cmd_options; cmd_options.env_diff = env_.env_diff_for_binaries; diff --git a/centipede/dispatcher_flag_helper.h b/centipede/dispatcher_flag_helper.h index d14383143..e6a4d16fb 100644 --- a/centipede/dispatcher_flag_helper.h +++ b/centipede/dispatcher_flag_helper.h @@ -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; } }; diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc index 3c95801fd..514d13b8a 100644 --- a/centipede/engine_worker.cc +++ b/centipede/engine_worker.cc @@ -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() { @@ -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; } @@ -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 has_failure_output = false; @@ -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(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; } diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc index 0e1238269..4399dcb0b 100644 --- a/centipede/minimize_crash.cc +++ b/centipede/minimize_crash.cc @@ -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()); @@ -163,6 +164,7 @@ void MinimizeCrash(ByteSpan crashy_input, const Environment& env, ThreadPool threads{static_cast(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); }); } diff --git a/centipede/runner.cc b/centipede/runner.cc index 40e4f39bf..3715b8660 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -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); } @@ -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.// // @@ -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); diff --git a/centipede/sancov_state.h b/centipede/sancov_state.h index bde30e12f..e81004132 100644 --- a/centipede/sancov_state.h +++ b/centipede/sancov_state.h @@ -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(flag_helper.HasIntFlag(":shmem_size_mb=", 0)); SancovFlags flags = { /*path_level=*/std::min(ThreadLocalSancovState::kBoundedPathLength, diff --git a/centipede/shared_memory_blob_sequence.cc b/centipede/shared_memory_blob_sequence.cc index 3a2412649..51415df13 100644 --- a/centipede/shared_memory_blob_sequence.cc +++ b/centipede/shared_memory_blob_sequence.cc @@ -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) { @@ -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(); } diff --git a/centipede/shared_memory_blob_sequence.h b/centipede/shared_memory_blob_sequence.h index fc69c10dd..b5d781e3c 100644 --- a/centipede/shared_memory_blob_sequence.h +++ b/centipede/shared_memory_blob_sequence.h @@ -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) { @@ -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(); diff --git a/centipede/shared_memory_blob_sequence_test.cc b/centipede/shared_memory_blob_sequence_test.cc index 2b9f55799..f5dd4fc45 100644 --- a/centipede/shared_memory_blob_sequence_test.cc +++ b/centipede/shared_memory_blob_sequence_test.cc @@ -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)); @@ -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); } } diff --git a/centipede/util.cc b/centipede/util.cc index ab2eb19ef..df169a74a 100644 --- a/centipede/util.cc +++ b/centipede/util.cc @@ -383,4 +383,11 @@ int PollTimeoutMs(absl::Duration timeout) { return static_cast(ms); } +std::string EngineFlagEscape(std::string_view value) { + return absl::StrReplaceAll(value, { + {":", "\\:"}, + {"\\", "\\\\"}, + }); +} + } // namespace fuzztest::internal diff --git a/centipede/util.h b/centipede/util.h index 4905c68b4..d0f04c4d8 100644 --- a/centipede/util.h +++ b/centipede/util.h @@ -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_