blob: e1f27f962431637bea5c2b74ad82ab29a090da38 [file] [log] [blame] [edit]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/sys/fuzzing/common/shared-memory.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/vmar.h>
#include <stdint.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <limits>
#include <fbl/algorithm.h>
#include <sanitizer/asan_interface.h>
namespace fuzzing {
namespace {
// These are the flags that the shared memory should be mapped with.
const zx_vm_option_t kOptions =
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_MAP_RANGE | ZX_VM_REQUIRE_NON_RESIZABLE;
// If size is |kInlinedSize|, buffer starts with an inline header.
const char* kInlineMagic = "INLINED";
} // namespace
// Helper function to suppress linter warning.
template <typename T>
void Cast(zx_vaddr_t addr, T** out) {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
*out = reinterpret_cast<T*>(addr);
}
// Public methods
SharedMemory::~SharedMemory() { Reset(); }
SharedMemory& SharedMemory::operator=(SharedMemory&& other) noexcept {
vmo_ = std::move(other.vmo_);
mapped_addr_ = other.mapped_addr_;
mapped_size_ = other.mapped_size_;
data_ = other.data_;
size_ = other.size_;
header_ = other.header_;
mirror_ = other.mirror_;
poisoning_ = other.poisoning_;
unpoisoned_size_ = other.unpoisoned_size_;
other.mapped_addr_ = 0;
other.poisoning_ = false;
other.Reset();
return *this;
}
size_t SharedMemory::size() {
if (header_ && header_->size != size_) {
size_ = header_->size;
PoisonAfter(size_);
}
return size_;
}
void SharedMemory::Reserve(size_t capacity) {
Create(sizeof(InlineHeader) + capacity);
Map();
Cast(mapped_addr_, &header_);
memcpy(header_->magic, kInlineMagic, sizeof(header_->magic));
header_->size = 0;
Cast(mapped_addr_ + sizeof(InlineHeader), &data_);
size_ = 0;
unpoisoned_size_ = this->capacity();
}
void SharedMemory::Mirror(void* data, size_t size) {
FX_CHECK(data && size);
Create(size);
Map();
mirror_ = data;
Cast(mapped_addr_, &data_);
size_ = size;
unpoisoned_size_ = capacity();
Update();
}
Buffer SharedMemory::Share() {
Buffer buf;
buf.size = header_ ? mapped_size_ : size_;
auto status = vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &buf.vmo);
if (status != ZX_OK) {
FX_LOGS(FATAL) << "Failed to duplicate VMO: " << zx_status_get_string(status);
}
return buf;
}
void SharedMemory::LinkReserved(Buffer&& buf) {
Reset();
Map(std::move(buf));
Cast(mapped_addr_, &header_);
if (strncmp(header_->magic, kInlineMagic, sizeof(header_->magic)) != 0) {
FX_LOGS(FATAL) << "Bad inline header: magic=0x" << std::hex << header_->magic << std::dec;
}
Cast(mapped_addr_ + sizeof(InlineHeader), &data_);
unpoisoned_size_ = capacity();
}
void SharedMemory::LinkMirrored(Buffer&& buf) {
Reset();
size_ = buf.size;
Map(std::move(buf));
Cast(mapped_addr_, &data_);
unpoisoned_size_ = capacity();
}
void SharedMemory::SetPoisoning(bool enable) {
if (enable != poisoning_) {
PoisonAfter(enable ? size_ : capacity());
poisoning_ = enable;
}
}
void SharedMemory::Resize(size_t size) {
FX_DCHECK(header_);
FX_DCHECK(size <= capacity());
size_ = size;
header_->size = size_;
}
void SharedMemory::Write(uint8_t one_byte) {
FX_DCHECK(header_);
FX_DCHECK(sizeof(*header_) + size_ < mapped_size_);
FX_DCHECK(!poisoning_);
data_[size_++] = one_byte;
header_->size = size_;
}
void SharedMemory::Write(const void* data, size_t size) {
if (!size) {
return;
}
FX_DCHECK(header_);
FX_DCHECK(sizeof(*header_) + size_ + size <= mapped_size_);
FX_DCHECK(!poisoning_);
memcpy(data_ + size_, data, size);
size_ += size;
header_->size = size_;
}
void SharedMemory::Update() {
FX_DCHECK(mirror_);
memcpy(data_, mirror_, size_);
}
void SharedMemory::Clear() {
if (header_) {
SetPoisoning(false);
Resize(0);
} else {
memset(mirror_, 0, size_);
}
}
// Private methods
void SharedMemory::Reset() {
SetPoisoning(false);
if (is_mapped()) {
zx::vmar::root_self()->unmap(mapped_addr_, mapped_size_);
}
vmo_.reset();
mapped_addr_ = 0;
mapped_size_ = 0;
data_ = nullptr;
size_ = 0;
header_ = nullptr;
mirror_ = nullptr;
unpoisoned_size_ = 0;
}
void SharedMemory::Create(size_t capacity) {
Reset();
mapped_size_ = fbl::round_up(capacity, ZX_PAGE_SIZE);
auto status = zx::vmo::create(mapped_size_, 0, &vmo_);
if (status != ZX_OK) {
FX_LOGS(FATAL) << "Failed to create VMO: " << zx_status_get_string(status);
}
}
void SharedMemory::Map(Buffer&& buf) {
vmo_ = std::move(buf.vmo);
mapped_size_ = fbl::round_up(buf.size, ZX_PAGE_SIZE);
Map();
}
void SharedMemory::Map() {
FX_DCHECK(!mapped_addr_);
auto status = zx::vmar::root_self()->map(kOptions, 0, vmo_, 0, mapped_size_, &mapped_addr_);
if (status != ZX_OK) {
FX_LOGS(FATAL) << "Failed to map VMO: " << zx_status_get_string(status);
}
}
void SharedMemory::PoisonAfter(size_t size) {
auto max_size = capacity();
if (unpoisoned_size_ == size) {
return;
}
if (unpoisoned_size_ < max_size) {
ASAN_UNPOISON_MEMORY_REGION(data_ + unpoisoned_size_, max_size - unpoisoned_size_);
}
if (size < max_size) {
ASAN_POISON_MEMORY_REGION(data_ + size, max_size - size);
}
unpoisoned_size_ = size;
}
} // namespace fuzzing