blob: 0021d6fad5ad0777312ed6e4bccb9a4ab67ded16 [file]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <variant>
#include <vector>
#include "fuzzer/driver.h"
#include "fuzzer/fuzzer.h"
#include "ipcz/ipcz.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "third_party/abseil-cpp/absl/types/span.h"
#include "util/ref_counted.h"
#include "util/unsafe_buffers.h"
namespace ipcz::fuzzer {
namespace {
Fuzzer* fuzzer_instance;
class Transmission {
public:
Transmission(absl::Span<const uint8_t> data,
absl::Span<const IpczDriverHandle> handles)
: data_(data.begin(), data.end()),
handles_(handles.begin(), handles.end()) {}
Transmission(Transmission&& other) {
data_.swap(other.data_);
handles_.swap(other.handles_);
}
Transmission& operator=(Transmission&& other) {
Reset();
data_.swap(other.data_);
handles_.swap(other.handles_);
return *this;
}
~Transmission() { Reset(); }
void Dispatch(Fuzzer& fuzzer,
IpczHandle listener,
IpczTransportActivityHandler handler) {
fuzzer.DispatchTransportActivity(listener, handler, data_, handles_);
handles_.clear();
}
private:
void Reset() {
for (auto handle : handles_) {
GetFuzzerDriver().Close(handle, IPCZ_NO_FLAGS, nullptr);
}
data_.clear();
handles_.clear();
}
std::vector<uint8_t> data_;
std::vector<IpczDriverHandle> handles_;
};
} // namespace
// A trivial heap-based "shared memory" implementation.
class Fuzzer::Memory : public DriverObjectImpl<DriverObject::Type::kMemory> {
public:
explicit Memory(size_t num_bytes) : bytes_(num_bytes) {}
absl::Span<uint8_t> bytes() { return absl::MakeSpan(bytes_); }
private:
~Memory() override = default;
std::vector<uint8_t> bytes_;
};
// Owns the activity queues and other state pertaining to both ends of an
// entangled driver transport pair. Every Transport object references one side
// of a TransportBackend.
class Fuzzer::TransportBackend : public RefCounted<Fuzzer::TransportBackend> {
public:
TransportBackend(Fuzzer& fuzzer, bool may_use_broker_relay)
: fuzzer_(fuzzer), may_use_broker_relay_(may_use_broker_relay) {}
// Indicates whether or not this backend's endpoints may force object
// brokering upon transmission, in order to provide coverage of that code
// path within ipcz. Only set on transports that go between two non-broker
// nodes, and used exclusively in Fuzzer::Serialize().
bool may_use_broker_relay() const { return may_use_broker_relay_; }
// Closes one side (0 or 1) of this backend.
void Close(int side) {
auto& e = endpoints_[side];
e.is_closed = true;
e.handler = nullptr;
e.activity.clear();
if (!endpoints_[1 - side].is_closed) {
endpoints_[1 - side].activity.push_back(Error{});
}
}
// Enables a side (0 or 1) to dispatch activity during subsequent flushes.
void Activate(int side,
IpczHandle listener,
IpczTransportActivityHandler handler) {
auto& e = endpoints_[side];
e.listener = listener;
e.handler = handler;
}
// Marks a side (0 or 1) as deactivated, which will cause its handler to be
// reset and activity to stop dispatching at the end of the next flush.
void Deactivate(int side) { endpoints_[side].is_deactivated = true; }
// Enqueues a transmission in the opposite side's queue.
void EnqueueTransmissionFrom(int side,
absl::Span<const uint8_t> data,
absl::Span<const IpczDriverHandle> handles) {
if (!endpoints_[1 - side].is_closed) {
endpoints_[1 - side].activity.push_back(Transmission(data, handles));
}
}
// Dispatches all pending transmissions and errors to active endpoints, and
// finishes deactivating any which are marked for deactivation. Returns true
// iff any interesting work was done during the flush.
bool FlushActivity() {
bool did_work = false;
did_work |= FlushEndpoint(endpoints_[0]);
did_work |= FlushEndpoint(endpoints_[1]);
did_work |= MaybeFinalizeDeactivation(endpoints_[0]);
did_work |= MaybeFinalizeDeactivation(endpoints_[1]);
return did_work;
}
private:
friend class RefCounted<TransportBackend>;
struct Error {};
using Activity = std::variant<Transmission, Error>;
struct Endpoint {
bool is_closed = false;
bool is_deactivated = false;
std::vector<Activity> activity;
IpczHandle listener = IPCZ_INVALID_HANDLE;
IpczTransportActivityHandler handler = nullptr;
};
~TransportBackend() = default;
bool FlushEndpoint(Endpoint& e) {
if (!e.handler || e.activity.empty()) {
// Either not activated yet, or already closed, or simply idle.
return false;
}
std::vector<Activity> activity;
activity.swap(e.activity);
for (auto& entry : activity) {
if (std::holds_alternative<Error>(entry)) {
e.handler(e.listener, nullptr, 0, nullptr, 0,
IPCZ_TRANSPORT_ACTIVITY_ERROR, nullptr);
} else {
std::get<Transmission>(entry).Dispatch(fuzzer_, e.listener, e.handler);
}
}
return true;
}
bool MaybeFinalizeDeactivation(Endpoint& e) {
if (!e.is_deactivated || !e.handler) {
return false;
}
const auto listener = std::exchange(e.listener, IPCZ_INVALID_HANDLE);
const auto handler = std::exchange(e.handler, nullptr);
handler(listener, nullptr, 0, nullptr, 0,
IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED, nullptr);
return true;
}
Fuzzer& fuzzer_;
const bool may_use_broker_relay_;
std::array<Endpoint, 2> endpoints_ = {};
};
// One side (0 or 1) of a TransportBackend.
class Fuzzer::Transport
: public DriverObjectImpl<DriverObject::Type::kTransport> {
public:
Transport(Ref<TransportBackend> backend, int side)
: backend_(std::move(backend)), side_(side) {}
TransportBackend& backend() { return *backend_; }
IpczResult Close() override {
backend_->Close(side_);
return IPCZ_RESULT_OK;
}
void Activate(IpczHandle listener, IpczTransportActivityHandler handler) {
backend_->Activate(side_, listener, handler);
}
void Deactivate() { backend_->Deactivate(side_); }
void Transmit(absl::Span<const uint8_t> data,
absl::Span<const IpczDriverHandle> handles) {
backend_->EnqueueTransmissionFrom(side_, data, handles);
}
private:
~Transport() override = default;
const Ref<TransportBackend> backend_;
const int side_;
};
Fuzzer::Fuzzer() {
ABSL_HARDENING_ASSERT(fuzzer_instance == nullptr);
fuzzer_instance = this;
}
Fuzzer::Fuzzer(const FuzzConfig& config,
absl::Span<const unsigned char> fuzz_data)
: config_(config), fuzz_data_(fuzz_data) {
ABSL_HARDENING_ASSERT(fuzzer_instance == nullptr);
fuzzer_instance = this;
}
Fuzzer::~Fuzzer() {
FlushTransports();
memory_objects_.clear();
driver_objects_.clear();
transport_backends_.clear();
ABSL_HARDENING_ASSERT(fuzzer_instance == this);
fuzzer_instance = nullptr;
}
Fuzzer& Fuzzer::Get() {
ABSL_HARDENING_ASSERT(fuzzer_instance);
return *fuzzer_instance;
}
void Fuzzer::FlushTransports() {
bool did_work;
do {
did_work = false;
// NOTE: Flushing may mutate the `transport_backends_`, so it's important
// not to iterate in-place. Since we'll loop until no new work is done, it's
// fine to iterate over a potentially stale copy.
const auto backends = transport_backends_;
for (auto& backend : backends) {
did_work |= backend->FlushActivity();
}
} while (did_work);
}
IpczDriverHandle Fuzzer::RegisterDriverObject(Ref<DriverObject> object) {
const IpczDriverHandle handle = next_driver_handle_++;
driver_objects_[handle] = std::move(object);
return handle;
}
void Fuzzer::DispatchTransportActivity(
IpczHandle listener,
IpczTransportActivityHandler handler,
absl::Span<const uint8_t> data,
absl::Span<const IpczDriverHandle> handles) {
std::vector<uint8_t> modified_data;
const int this_transmission = current_transmission_++;
if (this_transmission == config_.transmission_to_fuzz &&
!fuzz_data_.empty()) {
if (config_.fuzz_link_memory) {
InjectFuzzDataIntoMemory();
} else {
const size_t offset = 4ul * config_.target_offset;
modified_data.resize(std::max(fuzz_data_.size() + offset, data.size()));
std::copy(data.begin(), data.end(), modified_data.begin());
std::copy(fuzz_data_.begin(), fuzz_data_.end(),
modified_data.begin() + offset);
data = absl::MakeSpan(modified_data);
}
}
const IpczResult result =
handler(listener, data.data(), data.size(), handles.data(),
handles.size(), IPCZ_NO_FLAGS, nullptr);
if (result != IPCZ_RESULT_OK && result != IPCZ_RESULT_UNIMPLEMENTED) {
handler(listener, nullptr, 0, nullptr, 0, IPCZ_TRANSPORT_ACTIVITY_ERROR,
nullptr);
}
}
IpczResult Fuzzer::Close(IpczDriverHandle handle) {
auto it = driver_objects_.find(handle);
if (it == driver_objects_.end()) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
Ref<DriverObject> object = std::move(it->second);
driver_objects_.erase(it);
return object->Close();
}
IpczResult Fuzzer::Serialize(IpczDriverHandle handle,
IpczDriverHandle transport_handle,
volatile void* data,
size_t* num_bytes,
IpczDriverHandle* handles,
size_t* num_handles) {
const Ref<DriverObject> object = GetDriverObject(handle);
if (!object) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
// Ensure we exercise object relay through a broker sometimes, since this
// covers a few dedicated node message types. Arbitrarily force it for odd-
// numbered handles whenever the transport allows.
if (handle & 1) {
if (transport_handle == IPCZ_INVALID_DRIVER_HANDLE) {
return IPCZ_RESULT_ABORTED;
}
const auto transport = GetDriverObjectAs<Transport>(transport_handle);
if (!transport) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
if (transport->backend().may_use_broker_relay()) {
return IPCZ_RESULT_PERMISSION_DENIED;
}
}
// Handle serialization is otherwise a trivial pass-through.
const size_t capacity = num_handles ? *num_handles : 0;
if (num_handles) {
*num_handles = 1;
}
if (capacity < 1) {
return IPCZ_RESULT_RESOURCE_EXHAUSTED;
}
handles[0] = handle;
return IPCZ_RESULT_OK;
}
IpczResult Fuzzer::Deserialize(const volatile void* data,
size_t num_bytes,
const IpczDriverHandle* handles,
size_t num_handles,
IpczDriverHandle transport,
IpczDriverHandle* handle) {
if (num_handles != 1) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
if (!GetDriverObject(handles[0])) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
*handle = handles[0];
return IPCZ_RESULT_OK;
}
IpczResult Fuzzer::CreateTransports(IpczDriverHandle* transport0,
IpczDriverHandle* transport1,
bool may_use_broker_relay) {
auto backend = MakeRefCounted<TransportBackend>(*this, may_use_broker_relay);
*transport0 = RegisterDriverObject(MakeRefCounted<Transport>(backend, 0));
*transport1 = RegisterDriverObject(MakeRefCounted<Transport>(backend, 1));
transport_backends_.push_back(std::move(backend));
return IPCZ_RESULT_OK;
}
IpczResult Fuzzer::ActivateTransport(IpczDriverHandle transport_handle,
IpczHandle listener,
IpczTransportActivityHandler handler) {
if (auto transport = GetDriverObjectAs<Transport>(transport_handle)) {
transport->Activate(listener, handler);
return IPCZ_RESULT_OK;
}
return IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Fuzzer::DeactivateTransport(IpczDriverHandle transport_handle) {
if (auto transport = GetDriverObjectAs<Transport>(transport_handle)) {
transport->Deactivate();
return IPCZ_RESULT_OK;
}
return IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Fuzzer::Transmit(IpczDriverHandle transport_handle,
const void* data,
size_t num_bytes,
const IpczDriverHandle* handles,
size_t num_handles) {
if (auto transport = GetDriverObjectAs<Transport>(transport_handle)) {
transport->Transmit(
absl::Span(static_cast<const uint8_t*>(data), num_bytes),
absl::Span(handles, num_handles));
return IPCZ_RESULT_OK;
}
return IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Fuzzer::AllocateSharedMemory(size_t num_bytes,
IpczDriverHandle* memory) {
auto new_memory = MakeRefCounted<Memory>(num_bytes);
*memory = RegisterDriverObject(new_memory);
memory_objects_.push_back(std::move(new_memory));
return IPCZ_RESULT_OK;
}
IpczResult Fuzzer::GetSharedMemoryInfo(IpczDriverHandle memory_handle,
IpczSharedMemoryInfo* info) {
if (auto memory = GetDriverObjectAs<Memory>(memory_handle)) {
info->region_num_bytes = memory->bytes().size();
return IPCZ_RESULT_OK;
}
return IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Fuzzer::DuplicateSharedMemory(IpczDriverHandle memory_handle,
IpczDriverHandle* duplicate) {
if (auto memory = GetDriverObjectAs<Memory>(memory_handle)) {
*duplicate = RegisterDriverObject(memory);
return IPCZ_RESULT_OK;
}
return IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Fuzzer::MapSharedMemory(IpczDriverHandle memory_handle,
volatile void** address,
IpczDriverHandle* mapping) {
if (auto memory = GetDriverObjectAs<Memory>(memory_handle)) {
*address = const_cast<volatile uint8_t*>(memory->bytes().data());
*mapping = RegisterDriverObject(memory);
return IPCZ_RESULT_OK;
}
return IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Fuzzer::GenerateRandomBytes(absl::Span<uint8_t> bytes) {
// Our "random" bytes are deterministic and not random. This ensures that
// node names (and ultimately, the contents of well-formed messages throughout
// fuzzer testcases) are consistent across runs.
for (uint8_t& byte : bytes) {
byte = next_random_byte_++;
}
return IPCZ_RESULT_OK;
}
void Fuzzer::InjectFuzzDataIntoMemory() {
for (const auto& memory : memory_objects_) {
const auto bytes = memory->bytes();
const size_t offset = (4ul * config_.target_offset) % bytes.size();
const size_t size = std::min(bytes.size() - offset, fuzz_data_.size());
std::copy(fuzz_data_.begin(), IPCZ_UNSAFE_TODO(fuzz_data_.begin() + size),
IPCZ_UNSAFE_TODO(bytes.begin() + offset));
}
}
Ref<DriverObject> Fuzzer::GetDriverObject(IpczDriverHandle handle) const {
auto it = driver_objects_.find(handle);
if (it == driver_objects_.end()) {
return nullptr;
}
return it->second;
}
} // namespace ipcz::fuzzer