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
3 changes: 3 additions & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,7 @@ cc_library(
name = "engine_worker",
srcs = [
"engine_worker.cc",
"runner_utils.cc",
"runner_utils.h",
],
hdrs = ["engine_worker_abi.h"],
Expand Down Expand Up @@ -1956,13 +1957,15 @@ cc_test(
timeout = "long",
srcs = ["centipede_test.cc"],
data = [
"@com_google_fuzztest//centipede",
"@com_google_fuzztest//centipede/testing:abort_fuzz_target",
"@com_google_fuzztest//centipede/testing:async_failing_target",
"@com_google_fuzztest//centipede/testing:expensive_startup_fuzz_target",
"@com_google_fuzztest//centipede/testing:fuzz_target_with_config",
"@com_google_fuzztest//centipede/testing:fuzz_target_with_custom_mutator",
"@com_google_fuzztest//centipede/testing:hanging_fuzz_target",
"@com_google_fuzztest//centipede/testing:seeded_fuzz_target",
"@com_google_fuzztest//centipede/testing:test_binary_for_engine_testing",
"@com_google_fuzztest//centipede/testing:test_fuzz_target",
"@com_google_fuzztest//centipede/testing:test_input_filter",
],
Expand Down
40 changes: 40 additions & 0 deletions centipede/centipede_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1391,5 +1391,45 @@ TEST_F(CentipedeWithTemporaryLocalDir, ToleratesAsyncFailureInMutation) {
HasSubstr("Mutate() succeeded")));
}

TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInWorkerMode) {
TempCorpusDir tmp_dir{test_info_->name()};
Environment env;
env.workdir = tmp_dir.path();
env.binary = GetDataDependencyFilepath(
"centipede/testing/test_binary_for_engine_testing");
env.test_name = "some_test";
env.populate_binary_info = false;
env.fork_server = false;
env.persistent_mode = true;
env.exit_on_crash = true;
env.stop_at = absl::Now() + absl::Seconds(10);
fuzztest::internal::DefaultCallbacksFactory<
fuzztest::internal::CentipedeDefaultCallbacks>
callbacks;
EXPECT_DEATH(
[&] { std::exit(CentipedeMain(env, callbacks)); }(),
ContainsRegex("Failure *: INPUT FAILURE: some_failure_description"));
}

TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInStandaloneMode) {
const std::string centipede_path =
GetDataDependencyFilepath("centipede/centipede");
const std::string test_binary_path = GetDataDependencyFilepath(
"centipede/testing/test_binary_for_engine_testing");
// Create a temporary dir and enter it for running the test binary, because
// the test binary uses CWD as the engine workdir.
TempCorpusDir tmp_dir{test_info_->name()};
const auto test_command =
absl::StrCat("cd ", tmp_dir.path().string(),
" && env FUZZTEST_CENTIPEDE_BINARY_PATH=", centipede_path,
" ", test_binary_path);
EXPECT_DEATH(
[&] {
const int status = std::system(test_command.c_str());
std::exit(WEXITSTATUS(status));
}(),
ContainsRegex("Failure *: INPUT FAILURE: some_failure_description"));
}

} // namespace
} // namespace fuzztest::internal
137 changes: 131 additions & 6 deletions centipede/engine_worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <algorithm>
Expand Down Expand Up @@ -105,7 +107,7 @@ struct WorkerFlags {
};

// The first call of this function must be outside of signal handlers since it
// allocates memory (enforced by `GetWorkerFlagsEarly`). After that it would be
// allocates memory (enforced by `WorkerInitEarly`). After that it would be
// signal-safe.
//
// The worker flags format is `:(NAME=VALUE|SWITCH:)+`. `GetWorkerFlags`
Expand Down Expand Up @@ -137,10 +139,6 @@ const WorkerFlags& GetWorkerFlags() {
return worker_flags;
}

__attribute__((constructor(200))) void GetWorkerFlagsEarly() {
(void)GetWorkerFlags();
}

// `header` should be in the form of `FLAG_NAME=`.
//
// Extracts "value" as a null-terminated string from "\0FLAG_NAME=value\0" in
Expand Down Expand Up @@ -237,6 +235,9 @@ constexpr std::string_view kWorkerInputsBlobSequencePathFlagHeader =
"arg1="; // TODO: Use better flag names when standardizing the protocol.
constexpr std::string_view kWorkerOutputsBlobSequencePathFlagHeader =
"arg2="; // TODO: Use better flag names when standardizing the protocol.
constexpr std::string_view kWorkerPersistentModeSocketPathFlagHeader =
"persistent_mode_socket="; // TODO: Use better flag names when
// standardizing the protocol.

struct WorkerState {
std::atomic<bool> has_failure_output = false;
Expand Down Expand Up @@ -323,6 +324,64 @@ inline std::string_view ToStringView(const std::vector<uint8_t>& bytes) {
return {reinterpret_cast<const char*>(bytes.data()), bytes.size()};
}

// Zero initialized.
static int persistent_mode_socket;

__attribute__((constructor(200))) void WorkerInitEarly() {
const char* persistent_mode_socket_path =
GetWorkerFlag(kWorkerPersistentModeSocketPathFlagHeader);
if (persistent_mode_socket_path == nullptr) return;
persistent_mode_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (persistent_mode_socket < 0) {
WorkerLog(
"Failed to create persistent mode socket - not running persistent "
"mode.",
LogLnSync{});
return;
}

struct sockaddr_un addr{};
addr.sun_family = AF_UNIX;
const size_t socket_path_len = strlen(persistent_mode_socket_path);
WorkerCheck(
socket_path_len < sizeof(addr.sun_path),
"persistent mode socket path string must be fit in sockaddr_un.sun_path");
std::memcpy(addr.sun_path, persistent_mode_socket_path, socket_path_len);

int connect_ret = 0;
do {
connect_ret =
connect(persistent_mode_socket, (struct sockaddr*)&addr, sizeof(addr));
} while (connect_ret == -1 && errno == EINTR);
if (connect_ret == -1) {
WorkerLog("Failed to connect the persistent mode socket to ",
persistent_mode_socket_path, LogLnSync{});
(void)close(persistent_mode_socket);
persistent_mode_socket = -1;
}

int flags = fcntl(persistent_mode_socket, F_GETFD);
if (flags == -1) {
WorkerLog(
"fcntl(F_GETFD) failed on the persistent mode socket - exiting "
"persistent mode",
LogLnSync{});
(void)close(persistent_mode_socket);
persistent_mode_socket = -1;
}
flags |= FD_CLOEXEC;
if (fcntl(persistent_mode_socket, F_SETFD, flags) == -1) {
WorkerLog(
"fcntl(F_SETFD) failed on the persistent mode socket - exiting "
"persistent mode",
LogLnSync{});
(void)close(persistent_mode_socket);
persistent_mode_socket = -1;
}
WorkerLog("Persistent mode: connected to ", persistent_mode_socket_path,
LogLnSync{});
}

BlobSequence* GetInputsBlobSequence() {
static auto result = []() -> BlobSequence* {
if (!HasWorkerSwitchFlag("shmem")) {
Expand Down Expand Up @@ -695,6 +754,70 @@ const char* FuzzTestWorkerGetTestName() {
return test_name;
}

void HandlePersistentMode(const FuzzTestAdapter& adapter) {
auto* inputs_blobseq = GetInputsBlobSequence();
auto* outputs_blobseq = GetOutputsBlobSequence();
bool first = true;
while (true) {
PersistentModeRequest req;
if (!ReadAll(persistent_mode_socket, reinterpret_cast<char*>(&req), 1)) {
WorkerLog("Failed to read request from persistent mode socket: ",
LogErrNo{}, LogLnSync{});
return;
}
if (first) {
first = false;
WorkerLog("FuzzTest engine worker enter persistent mode", LogLnSync{});
} else {
// Reset stdout/stderr.
for (int fd = 1; fd <= 2; fd++) {
lseek(fd, 0, SEEK_SET);
// NOTE: Allow ftruncate() to fail by ignoring its return; that's okay
// 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{});
}
if (req == PersistentModeRequest::kExit) break;
WorkerCheck(req == PersistentModeRequest::kRunBatch,
"Unknown persistent mode request");

inputs_blobseq->Reset();
outputs_blobseq->Reset();

// Read the first blob. It indicates what further actions to take.
auto request_type_blob = inputs_blobseq->Read();
if (IsMutationRequest(request_type_blob)) {
inputs_blobseq->Reset();
WorkerDoMutate(adapter);
} else if (IsExecutionRequest(request_type_blob)) {
inputs_blobseq->Reset();
WorkerDoExecute(adapter);
} else {
WorkerCheck(false, "Unknown shmem request");
}

const int result =
GetWorkerState().has_finding.load(std::memory_order_relaxed)
? EXIT_FAILURE
: EXIT_SUCCESS;
if (!WriteAll(persistent_mode_socket,
reinterpret_cast<const char*>(&result), sizeof(result))) {
WorkerLog("Failed to write response to the persistent mode socket: ",
LogErrNo{}, LogLnSync{});
return;
}
}
}

FuzzTestWorkerStatus WorkerRun(const FuzzTestAdapterManager& manager) {
const auto& flags = GetWorkerFlags();
WorkerCheck(flags.present, "worker flags must present");
Expand Down Expand Up @@ -764,7 +887,9 @@ FuzzTestWorkerStatus WorkerRun(const FuzzTestAdapterManager& manager) {
WorkerCheck(adapter.FreeInput != nullptr, "FreeInput must be defined");
WorkerCheck(adapter.FreeCtx != nullptr, "FreeCtx must be defined");

if (action == WorkerAction::kTestGetSeeds) {
if (persistent_mode_socket > 0) {
HandlePersistentMode(adapter);
} else if (action == WorkerAction::kTestGetSeeds) {
WorkerDoGetSeeds(adapter);
} else if (action == WorkerAction::kTestMutate) {
WorkerDoMutate(adapter);
Expand Down
6 changes: 6 additions & 0 deletions centipede/runner_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@

namespace fuzztest::internal {

// Weak dummy definition of LSan interface in case LSan is missing.
__attribute__((weak)) void __lsan_register_root_region(const void* p,
size_t size) {}
__attribute__((weak)) void __lsan_unregister_root_region(const void* p,
size_t size) {}

void PrintErrorAndExitIf(bool condition, const char* absl_nonnull error) {
if (!condition) return;
fprintf(stderr, "error: %s\n", error);
Expand Down
15 changes: 5 additions & 10 deletions centipede/runner_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ bool ReadAll(int fd, char* data, size_t size);
// written, false otherwise due to errors.
bool WriteAll(int fd, const char* data, size_t size);

extern "C" void __lsan_register_root_region(const void* p, size_t size)
__attribute__((weak));
extern "C" void __lsan_unregister_root_region(const void* p, size_t size)
__attribute__((weak));
// Leak sanitizer interface functions.
extern "C" void __lsan_register_root_region(const void* p, size_t size);
extern "C" void __lsan_unregister_root_region(const void* p, size_t size);

// Wraps an object of `T` stored as a plain byte array with explicit
// construction/destruction. Needed for runner/dispatcher related global states
Expand Down Expand Up @@ -107,18 +106,14 @@ class ExplicitLifetime {
new (&space_) T(std::forward<Args>(args)...);
// Needed otherwise lsan may lose track of the pointers inside the object as
// it is in-place constructed from the byte array.
if (__lsan_register_root_region) {
__lsan_register_root_region(&space_, sizeof(space_));
}
__lsan_register_root_region(&space_, sizeof(space_));
}

// Destructs the actual object (without reclaiming the space). It can only be
// called at most once after recent `Construct()`.
void Destruct() {
get()->~T();
if (__lsan_unregister_root_region) {
__lsan_unregister_root_region(&space_, sizeof(space_));
}
__lsan_unregister_root_region(&space_, sizeof(space_));
}

// Gets the pointer to the actual object backed by a plain byte array. Using
Expand Down
11 changes: 11 additions & 0 deletions centipede/testing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,14 @@ sh_test(
"@com_google_fuzztest//centipede:test_util_sh",
],
)

cc_binary(
name = "test_binary_for_engine_testing",
srcs = ["test_binary_for_engine_testing.cc"],
deps = [
"@abseil-cpp//absl/strings",
"@com_google_fuzztest//centipede:engine_abi",
"@com_google_fuzztest//centipede:engine_controller_with_subprocess",
"@com_google_fuzztest//centipede:engine_worker",
],
)
Loading
Loading