diff --git a/centipede/BUILD b/centipede/BUILD index dcaabf1c8..108e5e032 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -1074,7 +1074,6 @@ RUNNER_LINKOPTS = [ RUNNER_DEPS = [ ":byte_array_mutator", ":callstack", - ":dispatcher_flag_helper", ":execution_metadata", ":feature", ":foreach_nonzero", @@ -1208,15 +1207,6 @@ cc_library( ], ) -cc_library( - name = "dispatcher_flag_helper", - hdrs = ["dispatcher_flag_helper.h"], - copts = DISABLE_SANCOV_COPTS, - deps = [ - "@abseil-cpp//absl/base:nullability", - ], -) - cc_library( name = "sancov_runtime", srcs = [ @@ -1238,7 +1228,6 @@ cc_library( copts = DISABLE_SANCOV_COPTS, deps = [ ":callstack", - ":dispatcher_flag_helper", ":engine_abi", ":execution_metadata", ":feature", @@ -1730,6 +1719,15 @@ cc_test( ], ) +cc_test( + name = "runner_utils_test", + srcs = ["runner_utils_test.cc"], + deps = [ + ":runner_utils", + "@googletest//:gtest_main", + ], +) + cc_binary( name = "command_test_helper", srcs = ["command_test_helper.cc"], 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..2f5a842d8 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) { @@ -648,9 +654,10 @@ bool CentipedeCallbacks::GetSeedsViaExternalBinary( FUZZTEST_CHECK(!error) << "Failed to create seed inputs directory " << output_dir << ": " << error.message(); - std::string centipede_runner_flags = absl::StrCat( - "CENTIPEDE_RUNNER_FLAGS=:dump_seed_inputs:test=", env_.test_name, - ":arg1=", output_dir.string(), ":"); + std::string centipede_runner_flags = + absl::StrCat("CENTIPEDE_RUNNER_FLAGS=:dump_seed_inputs:test=", + EngineFlagEscape(env_.test_name), + ":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 +723,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 deleted file mode 100644 index d14383143..000000000 --- a/centipede/dispatcher_flag_helper.h +++ /dev/null @@ -1,76 +0,0 @@ -// 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. - -#ifndef FUZZTEST_CENTIPEDE_DISPATCHER_FLAG_HELPER_H_ -#define FUZZTEST_CENTIPEDE_DISPATCHER_FLAG_HELPER_H_ - -#include - -#include -#include - -#include "absl/base/nullability.h" - -namespace fuzztest::internal { - -struct DispatcherFlagHelper { - // We don't use flags passed via argv so that argv flags can be passed - // directly to LLVMFuzzerInitialize, w/o filtering. The flags are separated - // with ':' on both sides, i.e. like this: ":flag1:flag2:flag3=value3". - // We do it this way to make the flag parsing code extremely simple. The - // interface is private between Centipede and the runner and may change. - DispatcherFlagHelper(const char *absl_nullable flags_) : flags(flags_) {} - - const char *absl_nullable flags; - - // Returns true iff `flag` is present. - // Typical usage: pass ":some_flag:", i.e. the flag name surrounded with ':'. - // TODO(ussuri): Refactor `char *` into a `string_view`. - bool HasFlag(const char *absl_nonnull flag) const { - if (!flags) return false; - return strstr(flags, flag) != nullptr; - } - - // If a flag=value pair is present, returns value, - // otherwise returns `default_value`. - // Typical usage: pass ":some_flag=". - // TODO(ussuri): Refactor `char *` into a `string_view`. - uint64_t HasIntFlag(const char *absl_nonnull flag, - uint64_t default_value) const { - if (!flags) return default_value; - const char *beg = strstr(flags, flag); - if (!beg) return default_value; - return atoll(beg + strlen(flag)); // NOLINT: can't use strto64, etc. - } - - // If a :flag=value: pair is present returns value, otherwise returns nullptr. - // The result is obtained by calling strndup, so make sure to save - // it in `this` to avoid a leak. - // Typical usage: pass ":some_flag=". - // TODO(ussuri): Refactor `char *` into a `string_view`. - 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); - 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); - } -}; - -} // namespace fuzztest::internal - -#endif // FUZZTEST_CENTIPEDE_DISPATCHER_FLAG_HELPER_H_ diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc index 3c95801fd..fb1ab64f0 100644 --- a/centipede/engine_worker.cc +++ b/centipede/engine_worker.cc @@ -99,82 +99,39 @@ inline void WorkerCheck(bool condition, std::string_view error) { std::_Exit(1); } } - -struct WorkerFlags { - bool present; - // length of the flags string, excluding the ending '\0'. - size_t len; - const char* str; -}; +const char* absl_nullable GetWorkerFlagsEnv() { + static const char* flags = []() -> const char* { + // TODO(xinhaoyuan): Rename the env name to FUZZTEST_WORKER_FLAGS. + if (const char* env = std::getenv("CENTIPEDE_RUNNER_FLAGS")) { + WorkerLog("Worker flags: ", env); + char* env_copy = strdup(env); + if (env_copy == nullptr) { + // This should rarely happen. + WorkerLog("Failed to copy the flags env due to allocation failure"); + std::_Exit(1); + } + return env_copy; + } + return nullptr; + }(); + return flags; +} // The first call of this function must be outside of signal handlers since it // allocates memory (enforced by `WorkerInitEarly`). After that it would be // signal-safe. -// -// 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() { - static auto worker_flags = []() -> WorkerFlags { - // TODO(xinhaoyuan): Rename the env name to FUZZTEST_WORKER_FLAGS. - const char* env_flags = std::getenv("CENTIPEDE_RUNNER_FLAGS"); - if (env_flags == nullptr) { - return {}; - } - const size_t len = strlen(env_flags); - char* str = reinterpret_cast(malloc(len + 1)); - if (str == nullptr) { - WorkerLog("Cannot allocate the worker flags", LogLnSync{}); +const EngineFlagHelper& GetWorkerFlags() { + static ExplicitLifetime worker_flags; + [[maybe_unused]] static bool construct_once = [] { + worker_flags.Construct(GetWorkerFlagsEnv()); + if (worker_flags->HasAllocationFailure()) { + // This should rarely happen. + WorkerLog("Failed to process the flags due to allocation failure."); 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; - } - return WorkerFlags{true, len, str}; + return true; }(); - return worker_flags; -} - -// `header` should be in the form of `FLAG_NAME=`. -// -// Extracts "value" as a null-terminated string from "\0FLAG_NAME=value\0" in -// the flags. Returns nullptr if it is not found. -const char* GetWorkerFlag(std::string_view header) { - if (header.empty()) return nullptr; - const auto& worker_flags = GetWorkerFlags(); - if (!worker_flags.present) return nullptr; - const auto flags = std::string_view{worker_flags.str, worker_flags.len}; - size_t pos = 0; - while (pos = flags.find(header, pos), - pos != flags.npos && pos + header.size() < flags.size()) { - if (pos > 0 && flags[pos - 1] == '\0') { - return worker_flags.str + pos + header.size(); - } - pos += header.size(); - } - return nullptr; -} - -// Checks whether "\0{name}\0" exists in the flags. -bool HasWorkerSwitchFlag(std::string_view name) { - if (name.empty()) return false; - const auto& worker_flags = GetWorkerFlags(); - if (!worker_flags.present) return false; - const auto flags = std::string_view{worker_flags.str, worker_flags.len}; - size_t pos = 0; - while (pos = flags.find(name, pos), - pos != flags.npos && pos + name.size() < flags.size()) { - if (pos > 0 && flags[pos - 1] == '\0' && flags[pos + name.size()] == '\0') { - return true; - } - pos += name.size(); - } - return false; + return *worker_flags; } template @@ -241,6 +198,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; @@ -277,8 +235,8 @@ bool WorkerEmitFailureOutput(std::string_view prefix, std::string_view message) { bool ignored = GetWorkerState().has_failure_output.exchange(true); if (!ignored) { - if (const char* failure_description_path = - GetWorkerFlag(kWorkerFailureDescriptionPathFlagHeader); + if (const char* failure_description_path = GetWorkerFlags().GetStringFlag( + kWorkerFailureDescriptionPathFlagHeader); failure_description_path != nullptr) { TrySetFileContents(failure_description_path, /*append=*/false, prefix, message); @@ -322,8 +280,8 @@ void WorkerEmitFinding(std::string_view description, if (!ignored) { WorkerCheck(WorkerEmitFailureOutput(/*prefix=*/"", description), "Failed to emit failure output for the finding"); - if (const char* finding_signature_path = - GetWorkerFlag(kWorkerFailureSignaturePathFlagHeader); + if (const char* finding_signature_path = GetWorkerFlags().GetStringFlag( + kWorkerFailureSignaturePathFlagHeader); finding_signature_path != nullptr) { TrySetFileContents(finding_signature_path, /*append=*/false, signature); @@ -351,7 +309,7 @@ static int persistent_mode_socket; __attribute__((constructor(200))) void WorkerInitEarly() { const char* persistent_mode_socket_path = - GetWorkerFlag(kWorkerPersistentModeSocketPathFlagHeader); + GetWorkerFlags().GetStringFlag(kWorkerPersistentModeSocketPathFlagHeader); if (persistent_mode_socket_path == nullptr) return; persistent_mode_socket = socket(AF_UNIX, SOCK_STREAM, 0); if (persistent_mode_socket < 0) { @@ -407,40 +365,48 @@ __attribute__((constructor(200))) void WorkerInitEarly() { LogLnSync{}); } +size_t GetShmemSize() { + static auto result = []() -> size_t { + const uint64_t shmem_size_mb = + GetWorkerFlags().GetIntFlag(kWorkerShmemSizeMbFlagHeader, 0); + return static_cast(shmem_size_mb) << 20; + }(); + 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); + GetWorkerFlags().GetStringFlag(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); + const char* output_path = GetWorkerFlags().GetStringFlag( + kWorkerOutputsBlobSequencePathFlagHeader); WorkerCheck(output_path != nullptr, "outputs blob sequence is missing"); - return new SharedMemoryBlobSequence(output_path); + return new SharedMemoryBlobSequence(output_path, shmem_size); }(); return result; } int GetCrossOverLevel() { static int result = []() { - const char* cross_over_level_str = GetWorkerFlag(kWorkerCrossOverLevel); - if (cross_over_level_str != nullptr) { - const int parsed = - atoi(cross_over_level_str); // NOLINT: can't use strto64, etc. - if (0 <= parsed && parsed <= 100) return parsed; - } + const uint64_t cross_over_level = + GetWorkerFlags().GetIntFlag(kWorkerCrossOverLevel, 50); + if (cross_over_level <= 100) return static_cast(cross_over_level); // Default return 50; }(); @@ -449,16 +415,16 @@ int GetCrossOverLevel() { std::optional GetWorkerAction() { static auto worker_action = []() -> std::optional { - if (HasWorkerSwitchFlag("dump_configuration")) { + if (GetWorkerFlags().HasSwitchFlag("dump_configuration")) { return WorkerAction::kNoOp; } - if (HasWorkerSwitchFlag("dump_binary_id")) { + if (GetWorkerFlags().HasSwitchFlag("dump_binary_id")) { return WorkerAction::kGetBinaryId; } - if (HasWorkerSwitchFlag("list_tests")) { + if (GetWorkerFlags().HasSwitchFlag("list_tests")) { return WorkerAction::kListTests; } - if (HasWorkerSwitchFlag("dump_seed_inputs")) { + if (GetWorkerFlags().HasSwitchFlag("dump_seed_inputs")) { return WorkerAction::kTestGetSeeds; } auto* inputs_blobseq = GetInputsBlobSequence(); @@ -499,7 +465,7 @@ FuzzTestInputSink GetInputSinkTo(std::vector& inputs) { void WorkerDoGetBinaryId(const FuzzTestAdapterManager& manager) { if (GetWorkerState().saved_binary_id.exchange(true)) return; const char* binary_id_output_path = - GetWorkerFlag(kWorkerBinaryIdOutputFlagHeader); + GetWorkerFlags().GetStringFlag(kWorkerBinaryIdOutputFlagHeader); WorkerCheck(binary_id_output_path != nullptr, "binary ID output path is not set"); std::vector binary_id; @@ -512,7 +478,7 @@ void WorkerDoGetBinaryId(const FuzzTestAdapterManager& manager) { void WorkerDoListCurrentTest(std::string_view test_name) { const char* test_listing_output_path = - GetWorkerFlag(kWorkerTestListingOutputFlagHeader); + GetWorkerFlags().GetStringFlag(kWorkerTestListingOutputFlagHeader); WorkerCheck(test_listing_output_path != nullptr, "binary ID output path is not set"); TrySetFileContents(test_listing_output_path, @@ -536,7 +502,7 @@ void WorkerDoGetSeeds(const FuzzTestAdapter& adapter) { } static const char* output_dir = - GetWorkerFlag(kWorkerTestGetSeedsOutputDirFlagHeader); + GetWorkerFlags().GetStringFlag(kWorkerTestGetSeedsOutputDirFlagHeader); WorkerCheck(output_dir != nullptr, "seeds output path must be specified"); for (size_t i = 0; i < seed_handles.size(); ++i) { @@ -845,7 +811,7 @@ void WorkerDoExecute(const FuzzTestAdapter& adapter) { const char* FuzzTestWorkerGetTestName() { static auto test_name = []() -> const char* { - return GetWorkerFlag(kWorkerTestNameFlagHeader); + return GetWorkerFlags().GetStringFlag(kWorkerTestNameFlagHeader); }(); return test_name; } @@ -872,15 +838,12 @@ void HandlePersistentMode(const FuzzTestAdapter& adapter) { // to happen when the stdout/stderr are not redirected to a file. (void)ftruncate(fd, 0); } - WorkerLog( - "FuzzTest engine worker (", - req == PersistentModeRequest::kExit ? "exiting persistent mode" - : "persistent mode batch", - "); flags: ", - GetWorkerFlags().present - ? std::string_view{GetWorkerFlags().str, GetWorkerFlags().len} - : "", - LogLnSync{}); + WorkerLog("FuzzTest engine worker (", + req == PersistentModeRequest::kExit ? "exiting persistent mode" + : "persistent mode batch", + "); flags: ", + GetWorkerFlagsEnv() != nullptr ? GetWorkerFlagsEnv() : "", + LogLnSync{}); } if (req == PersistentModeRequest::kExit) break; WorkerCheck(req == PersistentModeRequest::kRunBatch, @@ -917,10 +880,9 @@ void HandlePersistentMode(const FuzzTestAdapter& adapter) { } FuzzTestWorkerStatus WorkerRun(const FuzzTestAdapterManager& manager) { - const auto& flags = GetWorkerFlags(); - WorkerCheck(flags.present, "worker flags must present"); + WorkerCheck(GetWorkerFlagsEnv() != nullptr, "worker flags must present"); - if (HasWorkerSwitchFlag("dump_configuration")) { + if (GetWorkerFlags().HasSwitchFlag("dump_configuration")) { return kFuzzTestWorkerSuccess; } @@ -1018,13 +980,14 @@ FuzzTestWorkerStatus WorkerRun(const FuzzTestAdapterManager& manager) { namespace { using ::fuzztest::internal::GetWorkerFlags; +using ::fuzztest::internal::GetWorkerFlagsEnv; using ::fuzztest::internal::WorkerCheck; using ::fuzztest::internal::WorkerRun; } // namespace int FuzzTestWorkerIsRequired() { - static int result = GetWorkerFlags().present && + static int result = GetWorkerFlagsEnv() != nullptr && fuzztest::internal::GetWorkerAction().has_value(); return result; } diff --git a/centipede/runner.cc b/centipede/runner.cc index 40e4f39bf..0403dbe7c 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -51,7 +51,6 @@ #include "absl/base/optimization.h" #include "absl/types/span.h" #include "./centipede/byte_array_mutator.h" -#include "./centipede/dispatcher_flag_helper.h" #include "./centipede/execution_metadata.h" #include "./centipede/feature.h" #include "./centipede/mutation_data.h" @@ -810,7 +809,7 @@ static void SetLimits() { // No-op under ASAN/TSAN/MSAN - those may still rely on rss_limit_mb. if (vm_size_in_bytes < one_tb) { size_t address_space_limit_mb = - state->flag_helper.HasIntFlag(":address_space_limit_mb=", 0); + state->flag_helper.GetIntFlag("address_space_limit_mb=", 0); if (address_space_limit_mb > 0) { size_t limit_in_bytes = address_space_limit_mb << 20; struct rlimit rlimit_as = {limit_in_bytes, limit_in_bytes}; @@ -902,9 +901,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:")) { + state->run_time_flags.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, state->run_time_flags.shmem_size_mb << 20); StartSendingOutputsToEngine(outputs_blobseq); FinishSendingOutputsToEngine(outputs_blobseq); } @@ -969,7 +969,7 @@ static int HandlePersistentMode(RunnerCallbacks& callbacks, fprintf(stderr, "Centipede fuzz target runner (%s); flags: %s\n", req == PersistentModeRequest::kExit ? "exiting persistent mode" : "persistent mode batch", - state->flag_helper.flags); + CentipedeGetRunnerFlags()); } if (req == PersistentModeRequest::kExit) break; RunnerCheck(req == PersistentModeRequest::kRunBatch, @@ -987,9 +987,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 state->run_time_flags.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.// // @@ -998,25 +998,27 @@ int RunnerMain(int argc, char** argv, RunnerCallbacks& callbacks) { state->centipede_runner_main_executed = true; fprintf(stderr, "Centipede fuzz target runner; argv[0]: %s flags: %s\n", - argv[0], state->flag_helper.flags); + argv[0], CentipedeGetRunnerFlags()); - if (state->flag_helper.HasFlag(":dump_configuration:")) { + if (state->flag_helper.HasSwitchFlag("dump_configuration")) { DumpSerializedTargetConfigToFile(callbacks, /*output_file_path=*/sancov_state->arg1); return EXIT_SUCCESS; } - if (state->flag_helper.HasFlag(":dump_seed_inputs:")) { + if (state->flag_helper.HasSwitchFlag("dump_seed_inputs")) { // Seed request. DumpSeedsToDir(callbacks, /*output_dir=*/sancov_state->arg1); return EXIT_SUCCESS; } // Inputs / outputs from shmem. - if (state->flag_helper.HasFlag(":shmem:")) { + if (state->run_time_flags.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, state->run_time_flags.shmem_size_mb << 20); + SharedMemoryBlobSequence outputs_blobseq( + sancov_state->arg2, state->run_time_flags.shmem_size_mb << 20); // Persistent mode loop. if (state->persistent_mode_socket > 0) { return HandlePersistentMode(callbacks, inputs_blobseq, outputs_blobseq); @@ -1067,9 +1069,13 @@ extern "C" void CentipedeSetTimeoutPerInput(uint64_t timeout_per_input) { extern "C" __attribute__((weak)) const char* absl_nullable CentipedeGetRunnerFlags() { - if (const char* runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) - return strdup(runner_flags_env); - return nullptr; + static const char* flags = []() -> const char* { + if (const char* runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) { + return strdup(runner_flags_env); + } + return nullptr; + }(); + return flags; } // TODO: xinhaoyuan - write test for this. diff --git a/centipede/runner.h b/centipede/runner.h index 5ddc3b3e5..b64f732a1 100644 --- a/centipede/runner.h +++ b/centipede/runner.h @@ -19,10 +19,10 @@ #include #include +#include #include #include "./centipede/byte_array_mutator.h" -#include "./centipede/dispatcher_flag_helper.h" #include "./centipede/knobs.h" #include "./centipede/runner_interface.h" #include "./centipede/runner_result.h" @@ -38,6 +38,7 @@ struct RunTimeFlags { uint64_t ignore_timeout_reports : 1; uint64_t max_len; std::atomic stack_limit_kb; + size_t shmem_size_mb; }; // One global object of this type is created by the runner at start up. @@ -59,29 +60,30 @@ struct GlobalRunnerState { // Performs necessary cleanup on process termination. void OnTermination(); - DispatcherFlagHelper flag_helper = - DispatcherFlagHelper(CentipedeGetRunnerFlags()); + EngineFlagHelper flag_helper = EngineFlagHelper(CentipedeGetRunnerFlags()); // Note that this field reflects the initial runner flags. But some // flags can change later (if wrapped with std::atomic). RunTimeFlags run_time_flags = { - /*timeout_per_input=*/flag_helper.HasIntFlag(":timeout_per_input=", 0), - /*rss_limit_mb=*/flag_helper.HasIntFlag(":rss_limit_mb=", 0), - /*crossover_level=*/flag_helper.HasIntFlag(":crossover_level=", 50), + /*timeout_per_input=*/flag_helper.GetIntFlag("timeout_per_input=", 0), + /*rss_limit_mb=*/flag_helper.GetIntFlag("rss_limit_mb=", 0), + /*crossover_level=*/flag_helper.GetIntFlag("crossover_level=", 50), /*ignore_timeout_reports=*/ - flag_helper.HasFlag(":ignore_timeout_reports:"), - /*max_len=*/flag_helper.HasIntFlag(":max_len=", 4000), - /*stack_limit_kb=*/flag_helper.HasIntFlag(":stack_limit_kb=", 0), + flag_helper.HasSwitchFlag("ignore_timeout_reports"), + /*max_len=*/flag_helper.GetIntFlag("max_len=", 4000), + /*stack_limit_kb=*/flag_helper.GetIntFlag("stack_limit_kb=", 0), + /*shmem_size_mb=*/ + static_cast(flag_helper.GetIntFlag("shmem_size_mb=", 0)), }; // The path to a file where the runner may write the description of failure. - const char *failure_description_path = - flag_helper.GetStringFlag(":failure_description_path="); + const char* failure_description_path = + flag_helper.GetStringFlag("failure_description_path="); std::atomic has_failure_description; const char* persistent_mode_socket_path = - flag_helper.GetStringFlag(":persistent_mode_socket="); + flag_helper.GetStringFlag("persistent_mode_socket="); int persistent_mode_socket = 0; pthread_mutex_t execution_result_override_mu = PTHREAD_MUTEX_INITIALIZER; diff --git a/centipede/runner_interface.h b/centipede/runner_interface.h index 9b560bddd..7a2208b2c 100644 --- a/centipede/runner_interface.h +++ b/centipede/runner_interface.h @@ -80,7 +80,8 @@ extern "C" void CentipedeSetTimeoutPerInput(uint64_t timeout_per_input); // gets the flags from CENTIPEDE_RUNNER_FLAGS env var. // // It should return either a nullptr or a constant string that is valid -// throughout the entire process life-time. +// throughout the entire process life-time. Multiple calls should always return +// the same value. extern "C" const char* absl_nullable CentipedeGetRunnerFlags(); // An overridable function to override `LLVMFuzzerMutate` behavior. diff --git a/centipede/runner_utils.cc b/centipede/runner_utils.cc index 8010574ca..026e66482 100644 --- a/centipede/runner_utils.cc +++ b/centipede/runner_utils.cc @@ -96,4 +96,26 @@ bool WriteAll(int fd, const char* data, size_t size) { return true; } +size_t UnescapeEngineFlags(char* flags, size_t size) { + size_t r = 0; + size_t w = 0; + size_t cur_flag_beg = 0; + for (r = 0; r < size; ++r) { + if (flags[r] == ':') { + flags[w] = 0; + cur_flag_beg = ++w; + continue; + } + // Skip copying if no flag beg was scanned before. + if (cur_flag_beg == 0) continue; + if (flags[r] == '\\' && r + 1 < size) { + ++r; + } + flags[w] = flags[r]; + ++w; + } + if (cur_flag_beg < 2) return 0; + return cur_flag_beg; +} + } // namespace fuzztest::internal diff --git a/centipede/runner_utils.h b/centipede/runner_utils.h index 4b85afa6d..90a86360a 100644 --- a/centipede/runner_utils.h +++ b/centipede/runner_utils.h @@ -17,9 +17,13 @@ #include +#include #include #include +#include +#include #include +#include #include #include "absl/base/nullability.h" @@ -125,6 +129,80 @@ class ExplicitLifetime { alignas(T) unsigned char space_[sizeof(T)]; }; +// Unescape the `flags` buffer in-place, which comes with the format of +// :(NAME=VALUE:|NAME:)+, where NAME and VALUE can contain escaped chars with +// backslash (\). It drops any chars before the first colon or after the last +// unescaped colon, replace any unescaped colons with '\0'. Returns the number +// of chars in the unescaped result. +// +// Returns 0 if no proper flags are found in the `flags` buffer. +size_t UnescapeEngineFlags(char* flags, size_t size); + +// Helper class for processing and reading the engine flags. +class EngineFlagHelper { + public: + // Constructs the helper for a C-string `flags` with the format of :(ENTRY:)+. + explicit EngineFlagHelper(const char* absl_nullable flags) + : flags_(nullptr), size_(0), has_allocation_failure_(false) { + if (flags == nullptr) return; + flags_ = strdup(flags); + if (flags_ == nullptr) { + has_allocation_failure_ = true; + return; + } + size_ = UnescapeEngineFlags(flags_, strlen(flags_)); + } + + EngineFlagHelper(const EngineFlagHelper&) = delete; + EngineFlagHelper& operator=(const EngineFlagHelper&) = delete; + + ~EngineFlagHelper() { + if (flags_) { + free(flags_); + } + } + + bool HasAllocationFailure() const { return has_allocation_failure_; } + + bool HasSwitchFlag(std::string_view flag) const { + return FindEntry(flag, /*match_whole=*/true) != nullptr; + } + + uint64_t GetIntFlag(std::string_view header, uint64_t default_value) const { + const char* absl_nullable flag = GetStringFlag(header); + if (flag == nullptr) return default_value; + return atoll(flag); // NOLINT: can't use strto64, etc. + } + + const char* absl_nullable GetStringFlag(std::string_view header) const { + const char* absl_nullable entry = FindEntry(header); + if (entry == nullptr) return nullptr; + return entry + header.size(); + } + + private: + // Returns an entry in the flags for `flag`. If `match_whole` is set, match + // `flag` as the whole entry, otherwise match it as a prefix. + const char* absl_nullable FindEntry(std::string_view flag, + bool match_whole = false) const { + if (flags_ == nullptr || flag.empty()) return nullptr; + auto flags = std::string_view{flags_, size_}; + while (true) { + auto match = flags.find(flag); + if (match == flags.npos) return nullptr; + if ((match > 0 && flags[match - 1] == 0) && + (!match_whole || flags[match + flag.size()] == 0)) { + return flags.data() + match; + } + flags = flags.substr(match + flag.size()); + } + } + + char* absl_nullable flags_; + size_t size_; + bool has_allocation_failure_; +}; + } // namespace fuzztest::internal #endif // THIRD_PARTY_CENTIPEDE_RUNNER_UTILS_H_ diff --git a/centipede/runner_utils_test.cc b/centipede/runner_utils_test.cc new file mode 100644 index 000000000..190c269f6 --- /dev/null +++ b/centipede/runner_utils_test.cc @@ -0,0 +1,178 @@ +// Copyright 2026 Google LLC +// +// 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/runner_utils.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace fuzztest::internal { +namespace { + +using std::string_view_literals::operator""sv; + +TEST(RunnerUtilsTest, UnescapeEngineFlagsEmptyBuffer) { + char buf[] = ""; + EXPECT_EQ(UnescapeEngineFlags(buf, 0), 0); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsNoColons) { + std::string s = "no_colons_here"; + EXPECT_EQ(UnescapeEngineFlags(s.data(), s.size()), 0); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsSingleColon) { + std::string s1 = ":"; + EXPECT_EQ(UnescapeEngineFlags(s1.data(), s1.size()), 0); + + std::string s2 = ":only_leading"; + EXPECT_EQ(UnescapeEngineFlags(s2.data(), s2.size()), 0); + + std::string s3 = "only_trailing:"; + EXPECT_EQ(UnescapeEngineFlags(s3.data(), s3.size()), 0); + + std::string s4 = "middle:only"; + EXPECT_EQ(UnescapeEngineFlags(s4.data(), s4.size()), 0); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsEmptyFlags) { + { + std::string s = "::"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0\0"sv); + } + { + std::string s = ":::"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0\0\0"sv); + } +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsSingleFlag) { + { + std::string s = ":flag:"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag\0"sv); + } + { + std::string s = ":key=value:"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0key=value\0"sv); + } +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsMultipleFlags) { + std::string s = ":flag1:flag2=val2:flag3:"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag1\0flag2=val2\0flag3\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsEscapedColon) { + std::string s = R"(:flag=foo\:bar:flag2:)"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag=foo:bar\0flag2\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsEscapedBackslash) { + std::string s = R"(:path=C\:\\dir\\foo\\bar:flag2:)"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), + "\0path=C:\\dir\\foo\\bar\0flag2\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsEscapedOtherChars) { + std::string s = R"(:flag\=name=val\=123:)"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag=name=val=123\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsDropsCharsBeforeFirstColon) { + std::string s = "junk_before:flag1:flag2:"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag1\0flag2\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsDropsCharsAfterLastColon) { + std::string s = ":flag1:flag2:junk_after"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag1\0flag2\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsDropsCharsBeforeAndAfter) { + std::string s = "prefix:flag1=1:flag2=2:suffix"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag1=1\0flag2=2\0"sv); +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsTrailingBackslash) { + // Trailing backslash after the last valid colon. + { + std::string s = ":flag:\\"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0flag\0"sv); + } + + // Trailing backslash before colon could close. + { + std::string s = ":flag\\"; + EXPECT_EQ(UnescapeEngineFlags(s.data(), s.size()), 0); + } + + // Escaped colon at the end: ":flag\:" means the second colon is escaped, + // so there is no terminating unescaped colon. + { + std::string s = R"(:flag\:)"; + EXPECT_EQ(UnescapeEngineFlags(s.data(), s.size()), 0); + } +} + +TEST(RunnerUtilsTest, UnescapeEngineFlagsComplex) { + std::string s = R"(ignored:a=1\:2:b=\\c\\:d\=4:done:ignored_too)"; + const size_t len = UnescapeEngineFlags(s.data(), s.size()); + EXPECT_EQ(std::string_view(s.data(), len), "\0a=1:2\0b=\\c\\\0d=4\0done\0"sv); +} + +TEST(RunnerUtilsTest, EngineFlagHelperWorksWithoutFlags) { + EngineFlagHelper helper(nullptr); + EXPECT_FALSE(helper.HasSwitchFlag("foo")); + EXPECT_EQ(helper.GetIntFlag("bar=", 42), 42); + EXPECT_EQ(helper.GetStringFlag("baz="), nullptr); +} + +TEST(RunnerUtilsTest, EngineFlagHelperWorksWithFlags) { + EngineFlagHelper helper(":flag1:flag2=123:str=hello:"); + EXPECT_TRUE(helper.HasSwitchFlag("flag1")); + EXPECT_FALSE(helper.HasSwitchFlag("flag")); + EXPECT_FALSE(helper.HasSwitchFlag("flag1_extra")); + EXPECT_FALSE(helper.HasSwitchFlag("flag2")); + EXPECT_FALSE(helper.HasSwitchFlag("missing")); + + EXPECT_EQ(helper.GetIntFlag("flag2=", 0), 123); + EXPECT_EQ(helper.GetIntFlag("missing=", 999), 999); + + EXPECT_STREQ(helper.GetStringFlag("str="), "hello"); + EXPECT_EQ(helper.GetStringFlag("missing="), nullptr); +} + +TEST(RunnerUtilsTest, EngineFlagHelperGetsUnescapedFlags) { + EngineFlagHelper helper(R"(:path=C\:\\dir\\foo:flag\=name=val\=123:)"); + EXPECT_STREQ(helper.GetStringFlag("path="), "C:\\dir\\foo"); + EXPECT_STREQ(helper.GetStringFlag("flag=name="), "val=123"); +} + +} // namespace +} // namespace fuzztest::internal diff --git a/centipede/sancov_callbacks.cc b/centipede/sancov_callbacks.cc index 3e5d0c504..b1c180caf 100644 --- a/centipede/sancov_callbacks.cc +++ b/centipede/sancov_callbacks.cc @@ -23,7 +23,6 @@ #include "absl/base/nullability.h" #include "absl/base/optimization.h" -#include "./centipede/dispatcher_flag_helper.h" #include "./centipede/feature.h" #include "./centipede/int_utils.h" #include "./centipede/pc_info.h" @@ -356,7 +355,7 @@ static void UpdatePcCounterSetSizeAligned(size_t size) { static pthread_once_t main_object_lazy_init_once = PTHREAD_ONCE_INIT; static void MainObjectLazyInitOnceCallback() { sancov_state->main_object = fuzztest::internal::GetDlInfo( - sancov_state->flag_helper.GetStringFlag(":dl_path_suffix=")); + sancov_state->flag_helper.GetStringFlag("dl_path_suffix=")); fprintf(stderr, "MainObjectLazyInitOnceCallback %zx\n", sancov_state->main_object.start_address); UpdatePcCounterSetSizeAligned(sancov_state->reverse_pc_table.NumPcs()); diff --git a/centipede/sancov_state.cc b/centipede/sancov_state.cc index c5bbaf344..8adedeeaf 100644 --- a/centipede/sancov_state.cc +++ b/centipede/sancov_state.cc @@ -25,7 +25,6 @@ #include #include "absl/base/nullability.h" -#include "./centipede/dispatcher_flag_helper.h" #include "./centipede/engine_abi.h" #include "./centipede/execution_metadata.h" #include "./centipede/feature.h" @@ -197,7 +196,7 @@ void SancovState::CleanUpDetachedTls() { static void MaybePopulateReversePcTable() { const char* pcs_file_path = - sancov_state->flag_helper.GetStringFlag(":pcs_file_path="); + sancov_state->flag_helper.GetStringFlag("pcs_file_path="); if (!pcs_file_path) return; const auto pc_table = ReadBytesFromFilePath(pcs_file_path); sancov_state->reverse_pc_table.SetFromPCs(pc_table); @@ -253,8 +252,8 @@ static void DumpDsoTable(const char *absl_nonnull output_path) { SancovState::SancovState() { tls.OnThreadStart(); // Compute main_object. - main_object = GetDlInfo(flag_helper.GetStringFlag(":dl_path_suffix=")); - if (!sancov_state->main_object.IsSet()) { + main_object = GetDlInfo(flag_helper.GetStringFlag("dl_path_suffix=")); + if (!main_object.IsSet()) { fprintf( stderr, "Failed to compute main_object. This may happen" @@ -262,7 +261,7 @@ SancovState::SancovState() { } // Dump the binary info tables. - if (flag_helper.HasFlag(":dump_binary_info:")) { + if (flag_helper.HasSwitchFlag("dump_binary_info")) { RunnerCheck(arg1 && arg2 && arg3, "dump_binary_info requires 3 arguments"); if (!arg1 || !arg2 || !arg3) _exit(EXIT_FAILURE); DumpPcTable(arg1); @@ -561,10 +560,14 @@ const ExecutionMetadata& SanCovRuntimeGetExecutionMetadata() { } // namespace fuzztest::internal // Can be overridden to not depend explicitly on CENTIPEDE_RUNNER_FLAGS. -extern "C" __attribute__((weak)) const char *absl_nullable GetSancovFlags() { - if (const char *sancov_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) - return strdup(sancov_flags_env); - return nullptr; +extern "C" __attribute__((weak)) const char* absl_nullable GetSancovFlags() { + static const char* flags = []() -> const char* { + if (const char* sancov_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) { + return strdup(sancov_flags_env); + } + return nullptr; + }(); + return flags; } void SanCovRuntimeClearCoverage(bool full_clear) { diff --git a/centipede/sancov_state.h b/centipede/sancov_state.h index bde30e12f..5e105e0fa 100644 --- a/centipede/sancov_state.h +++ b/centipede/sancov_state.h @@ -31,7 +31,6 @@ #include "./centipede/callstack.h" #include "./centipede/concurrent_bitset.h" #include "./centipede/concurrent_byteset.h" -#include "./centipede/dispatcher_flag_helper.h" #include "./centipede/execution_metadata.h" #include "./centipede/feature.h" #include "./centipede/hashed_ring_buffer.h" @@ -42,7 +41,7 @@ #include "./centipede/sancov_object_array.h" #include "./centipede/sancov_runtime.h" -extern "C" const char *absl_nullable GetSancovFlags(); +extern "C" const char* absl_nullable GetSancovFlags(); namespace fuzztest::internal { @@ -150,27 +149,27 @@ struct SancovState { SancovState(); ~SancovState(); - DispatcherFlagHelper flag_helper = DispatcherFlagHelper(GetSancovFlags()); + EngineFlagHelper flag_helper = EngineFlagHelper(GetSancovFlags()); // TODO(xinhaoyuan): Change to use meaningful flag names instead of the // generic names arg1/2/3. - const char *arg1 = flag_helper.GetStringFlag(":arg1="); - const char *arg2 = flag_helper.GetStringFlag(":arg2="); - const char *arg3 = flag_helper.GetStringFlag(":arg3="); + const char* arg1 = flag_helper.GetStringFlag("arg1="); + const char* arg2 = flag_helper.GetStringFlag("arg2="); + const char* arg3 = flag_helper.GetStringFlag("arg3="); SancovFlags flags = { /*path_level=*/std::min(ThreadLocalSancovState::kBoundedPathLength, - flag_helper.HasIntFlag(":path_level=", 0)), - /*use_pc_features=*/flag_helper.HasFlag(":use_pc_features:"), + flag_helper.GetIntFlag("path_level=", 0)), + /*use_pc_features=*/flag_helper.HasSwitchFlag("use_pc_features"), /*use_dataflow_features=*/ - flag_helper.HasFlag(":use_dataflow_features:"), - /*use_cmp_features=*/flag_helper.HasFlag(":use_cmp_features:"), - /*callstack_level=*/flag_helper.HasIntFlag(":callstack_level=", 0), + flag_helper.HasSwitchFlag("use_dataflow_features"), + /*use_cmp_features=*/flag_helper.HasSwitchFlag("use_cmp_features"), + /*callstack_level=*/flag_helper.GetIntFlag("callstack_level=", 0), /*use_counter_features=*/ - flag_helper.HasFlag(":use_counter_features:"), + flag_helper.HasSwitchFlag("use_counter_features"), /*use_auto_dictionary=*/ - flag_helper.HasFlag(":use_auto_dictionary:"), - /*skip_seen_features=*/flag_helper.HasFlag(":skip_seen_features:"), + flag_helper.HasSwitchFlag("use_auto_dictionary"), + /*skip_seen_features=*/flag_helper.HasSwitchFlag("skip_seen_features"), }; // Computed by DlInfo(). 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_ diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index c74427e2c..1f460e5b7 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc @@ -1085,16 +1085,19 @@ class CentipedeCallbacksForRunnerFlagsExtraction } // namespace extern "C" const char* CentipedeGetRunnerFlags() { - if (const char* runner_flags_env = std::getenv("CENTIPEDE_RUNNER_FLAGS")) { - // Runner mode. Use the existing flags. - return strdup(runner_flags_env); - } + static const char* flags = []() -> const char* { + if (const char* runner_flags_env = std::getenv("CENTIPEDE_RUNNER_FLAGS")) { + // Runner mode. Use the existing flags. + return strdup(runner_flags_env); + } - // Set the runner flags according to the FuzzTest default environment. - const auto env = fuzztest::internal::CreateDefaultCentipedeEnvironment(); - CentipedeCallbacksForRunnerFlagsExtraction callbacks( - env, fuzztest::internal::global_stop_condition); - const std::string runner_flags = callbacks.GetRunnerFlagsContent(); - ABSL_VLOG(1) << "[.] Centipede runner flags: " << runner_flags; - return strdup(runner_flags.c_str()); + // Set the runner flags according to the FuzzTest default environment. + const auto env = fuzztest::internal::CreateDefaultCentipedeEnvironment(); + CentipedeCallbacksForRunnerFlagsExtraction callbacks( + env, fuzztest::internal::global_stop_condition); + const char* flags = strdup(callbacks.GetRunnerFlagsContent().c_str()); + FUZZTEST_VLOG(1) << "[.] Centipede runner flags: " << flags; + return flags; + }(); + return flags; }