blob: 2976cd1d47256bc510eef8e0b5f1f6fad3ae327f [file] [log] [blame]
// 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 THIRD_PARTY_CENTIPEDE_CENTIPEDE_CALLBACKS_H_
#define THIRD_PARTY_CENTIPEDE_CENTIPEDE_CALLBACKS_H_
#include <cstddef>
#include <filesystem> // NOLINT
#include <string>
#include <string_view>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/status/statusor.h"
#include "./centipede/binary_info.h"
#include "./centipede/byte_array_mutator.h"
#include "./centipede/command.h"
#include "./centipede/environment.h"
#include "./centipede/fuzztest_mutator.h"
#include "./centipede/mutation_input.h"
#include "./centipede/runner_result.h"
#include "./centipede/shared_memory_blob_sequence.h"
#include "./centipede/util.h"
#include "./common/defs.h"
namespace fuzztest::internal {
// User must inherit from this class and override at least the
// pure virtual functions.
//
// The classes inherited from this one must be thread-compatible.
// Note: the interface is not yet stable and may change w/o a notice.
class CentipedeCallbacks {
public:
// `env` is used to pass flags to `this`, it must outlive `this`.
CentipedeCallbacks(const Environment &env)
: env_(env),
byte_array_mutator_(env.knobs, GetRandomSeed(env.seed)),
fuzztest_mutator_(env.knobs, GetRandomSeed(env.seed)),
inputs_blobseq_(shmem_name1_.c_str(), env.shmem_size_mb << 20,
env.use_posix_shmem),
outputs_blobseq_(shmem_name2_.c_str(), env.shmem_size_mb << 20,
env.use_posix_shmem) {
if (env.use_legacy_default_mutator)
FUZZTEST_CHECK(byte_array_mutator_.set_max_len(env.max_len));
else
FUZZTEST_CHECK(fuzztest_mutator_.set_max_len(env.max_len));
}
virtual ~CentipedeCallbacks() {}
// Feeds `inputs` into the `binary`, for every input populates `batch_result`.
// Old contents of `batch_result` are cleared.
// Returns true on success, false on failure.
// Post-condition:
// `batch_result` has results for every `input`, even on failure.
virtual bool Execute(std::string_view binary,
const std::vector<ByteArray> &inputs,
BatchResult &batch_result) = 0;
// Takes non-empty `inputs` and returns at most `num_mutants` mutated inputs.
virtual std::vector<ByteArray> Mutate(
const std::vector<MutationInputRef> &inputs, size_t num_mutants) {
return env_.use_legacy_default_mutator
? byte_array_mutator_.MutateMany(inputs, num_mutants)
: fuzztest_mutator_.MutateMany(inputs, num_mutants);
}
// Populates the BinaryInfo using the `symbolizer_path` and `coverage_binary`
// in `env_`. The tables may not be populated if the PC table cannot be
// determined from the `coverage_binary` or if symbolization fails. Exits if
// PC table was not populated and `env_.require_pc_table` is set.
virtual void PopulateBinaryInfo(BinaryInfo &binary_info);
// Retrieves at most `num_seeds` seed inputs. Returns the number of seeds
// available if `num_seeds` had been large enough.
virtual size_t GetSeeds(size_t num_seeds, std::vector<ByteArray> &seeds) {
if (num_seeds > 0) seeds = {{0}};
return 1;
}
// Returns the configuration from the test target in the serialized form.
// Returns an empty string if the test target doesn't provide configuration.
virtual absl::StatusOr<std::string> GetSerializedTargetConfig() { return ""; }
// Requests any commands that are executing in persistent mode to
// end the execution and waits for them.
void CleanUpPersistentMode();
protected:
// Helpers that the user-defined class may use if needed.
// Same as ExecuteCentipedeSancovBinary, but uses shared memory.
// Much faster for fast targets since it uses fewer system calls.
int ExecuteCentipedeSancovBinaryWithShmem(
std::string_view binary, const std::vector<ByteArray> &inputs,
BatchResult &batch_result);
// Constructs a string CENTIPEDE_RUNNER_FLAGS=":flag1:flag2:...",
// where the flags are determined by `env` and also include `extra_flags`.
// If `disable_coverage`, coverage options are not added.
std::string ConstructRunnerFlags(std::string_view extra_flags = "",
bool disable_coverage = false);
// Uses an external binary `binary` to generate seed inputs. The binary should
// be linked against :centipede_runner and implement the RunnerCallbacks
// interface as described in runner_interface.h.
//
// Retrieves the first `seeds.size()` inputs (if exist) from `binary`,
// replacing the existing elements of `seeds`, and shrinking `seeds` if
// needed. Sets `num_avail_seeds` to the number of available seeds, which may
// be more than `seeds.size()`.
//
// Returns true on success.
bool GetSeedsViaExternalBinary(std::string_view binary,
size_t &num_avail_seeds,
std::vector<ByteArray> &seeds);
// Uses an external binary `binary` to get the serialized test target
// configuration. The binary should be linked against :centipede_runner and
// implement the RunnerCallbacks interface as described in runner_interface.h.
//
// If the binary returns with success and doesn't provide the configuration,
// sets `serialized_config` to empty string.
//
// Returns true on success.
bool GetSerializedTargetConfigViaExternalBinary(
std::string_view binary, std::string &serialized_config);
// Uses an external binary `binary` to mutate `inputs`. The binary
// should be linked against :centipede_runner and implement the
// RunnerCallbacks interface as described in runner_interface.h,
// or implement the legacy Structure-Aware Fuzzing interface described here:
// github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md
//
// Returns a `MutationResult` instance where `exit_code` indicates whether
// the binary was executed successfully, `has_custom_mutator` indicates
// whether the binary has a custom mutator, and if it does, `mutants` contains
// at most `num_mutants` non-empty mutants.
MutationResult MutateViaExternalBinary(
std::string_view binary, const std::vector<MutationInputRef> &inputs,
size_t num_mutants);
// Loads the dictionary from `dictionary_path`,
// returns the number of dictionary entries loaded.
size_t LoadDictionary(std::string_view dictionary_path);
protected:
const Environment &env_;
ByteArrayMutator byte_array_mutator_;
FuzzTestMutator fuzztest_mutator_;
private:
class PersistentModeServer;
struct CommandContext {
Command cmd;
// Opaque server for persistent mode. Nullptr if persistent mode is
// disabled.
std::unique_ptr<PersistentModeServer> persistent_mode_server;
~CommandContext();
};
// Returns a CommandContext with matching `binary`. Creates one if needed.
CommandContext& GetOrCreateCommandContextForBinary(std::string_view binary);
// Runs a batch with the command `binary` and returns the exit code.
int RunBatchForBinary(std::string_view binary);
// Prints the execution log from the last executed binary.
void PrintExecutionLog() const;
// Variables required for ExecuteCentipedeSancovBinaryWithShmem.
// They are computed in CTOR, to avoid extra computation in the hot loop.
std::string temp_dir_ = TemporaryLocalDirPath();
std::string temp_input_file_path_ =
std::filesystem::path(temp_dir_).append("temp_input_file");
const std::string execute_log_path_ =
std::filesystem::path(temp_dir_).append("log");
std::string failure_description_path_ =
std::filesystem::path(temp_dir_).append("failure_description");
std::string failure_signature_path_ =
std::filesystem::path(temp_dir_).append("failure_signature");
const std::string shmem_name1_ = ProcessAndThreadUniqueID("/ctpd-shm1-");
const std::string shmem_name2_ = ProcessAndThreadUniqueID("/ctpd-shm2-");
SharedMemoryBlobSequence inputs_blobseq_;
SharedMemoryBlobSequence outputs_blobseq_;
// Need unique_ptr indirection because CommandContext is not movable/copyable
// due to Command.
std::vector<std::unique_ptr<CommandContext>> command_contexts_;
};
// Abstract class for creating/destroying CentipedeCallbacks objects.
// A typical implementation would simply new/delete objects of appropriate type,
// see DefaultCallbacksFactory below.
// Other implementations (e.g. for tests) may take the object from elsewhere
// and not actually delete it.
class CentipedeCallbacksFactory {
public:
virtual CentipedeCallbacks *create(const Environment &env) = 0;
virtual void destroy(CentipedeCallbacks *callbacks) = 0;
virtual ~CentipedeCallbacksFactory() {}
};
// This is the typical way to implement a CentipedeCallbacksFactory for a Type.
template <typename Type>
class DefaultCallbacksFactory : public CentipedeCallbacksFactory {
public:
CentipedeCallbacks *create(const Environment &env) override {
return new Type(env);
}
void destroy(CentipedeCallbacks *callbacks) override { delete callbacks; }
};
// Creates a CentipedeCallbacks object in CTOR and destroys it in DTOR.
class ScopedCentipedeCallbacks {
public:
ScopedCentipedeCallbacks(CentipedeCallbacksFactory &factory,
const Environment &env)
: factory_(factory), callbacks_(factory_.create(env)) {}
~ScopedCentipedeCallbacks() { factory_.destroy(callbacks_); }
CentipedeCallbacks *absl_nonnull callbacks() { return callbacks_; }
private:
CentipedeCallbacksFactory &factory_;
CentipedeCallbacks *callbacks_;
};
} // namespace fuzztest::internal
#endif // THIRD_PARTY_CENTIPEDE_CENTIPEDE_CALLBACKS_H_