| // 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 "proxy.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/object.h> |
| #include <zircon/status.h> |
| |
| namespace fuzzing { |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // ProxyImpl |
| |
| namespace { |
| |
| const zx_signals_t kItemAdded = ZX_USER_SIGNAL_7; |
| |
| } // namespace |
| |
| // Public methods |
| |
| ProxyImpl::ProxyImpl(AggregatedProxy *aggregate) : aggregate_(aggregate) {} |
| |
| ProxyImpl::~ProxyImpl() {} |
| |
| void ProxyImpl::AddInline8BitCounters(Buffer inline_8bit_counters, |
| AddInline8BitCountersCallback callback) { |
| SharedMemory shmem; |
| if (Check(shmem.Link(inline_8bit_counters.vmo, inline_8bit_counters.size))) { |
| __sanitizer_cov_8bit_counters_init(shmem.begin<uint8_t>(), shmem.end<uint8_t>()); |
| mapped_.push_back(std::move(shmem)); |
| callback(); |
| } |
| } |
| |
| void ProxyImpl::AddPcTable(Buffer pc_table, AddPcTableCallback callback) { |
| SharedMemory shmem; |
| if (Check(shmem.Link(pc_table.vmo, pc_table.size))) { |
| __sanitizer_cov_pcs_init(shmem.begin<const uintptr_t>(), shmem.end<const uintptr_t>()); |
| mapped_.push_back(std::move(shmem)); |
| callback(); |
| } |
| } |
| |
| void ProxyImpl::AddTraces(zx::vmo traces, AddTracesCallback callback) { |
| if ((!traces_.is_mapped() || Check(ZX_ERR_BAD_STATE)) && |
| Check(traces_.Link(traces, kMaxInstructions * sizeof(Instruction))) && |
| Check(aggregate_->Add(traces_))) { |
| traces_.vmo().signal(kBetweenIterations | kReadableSignalA | kReadableSignalB, |
| kInIteration | kWritableSignalA | kWritableSignalB); |
| callback(); |
| } |
| } |
| |
| // Private methods |
| |
| bool ProxyImpl::Check(zx_status_t status) { |
| if (status != ZX_OK) { |
| aggregate_->Close(this, status); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // AggregatedProxy |
| |
| namespace { |
| |
| bool IsValidItem(zx_wait_item_t *item) { |
| return item->handle != ZX_HANDLE_INVALID && (item->pending & ZX_SIGNAL_HANDLE_CLOSED) == 0 && |
| zx_object_get_info(item->handle, ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr) == |
| ZX_OK; |
| } |
| |
| } // namespace |
| |
| // Public methods |
| |
| AggregatedProxy::AggregatedProxy() { Start(); } |
| |
| AggregatedProxy::~AggregatedProxy() { Stop(); } |
| |
| fidl::InterfaceRequestHandler<Proxy> AggregatedProxy::GetHandler() { |
| return [this](fidl::InterfaceRequest<Proxy> request) { |
| auto coverage = std::make_unique<ProxyImpl>(this); |
| bindings_.AddBinding(std::move(coverage), std::move(request)); |
| }; |
| } |
| |
| zx_status_t AggregatedProxy::CompleteIteration() { |
| sync_completion_reset(&sync_); |
| zx_status_t status; |
| if ((status = controller_.signal(0, kBetweenIterations)) != ZX_OK) { |
| return status; |
| } |
| if ((status = sync_completion_wait(&sync_, ZX_TIME_INFINITE)) != ZX_OK || |
| (status = controller_.signal(0, kInIteration)) != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| // Friend methods |
| |
| zx_status_t AggregatedProxy::Add(const SharedMemory &traces) { |
| std::lock_guard<std::mutex> lock(lock_); |
| size_t index = num_items_.load(); |
| if (index == ZX_WAIT_MANY_MAX_ITEMS || |
| num_distinguishers_ == std::numeric_limits<uint16_t>::max()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| // ProcessAll may have decreased num_items_, but it never increases it. Thus it's safe to use this |
| // index and simply store the new value, and rely on ProcessAll to re-compact if needed. |
| items_[index].handle = traces.vmo().get(); |
| items_[index].waitfor = kReadableSignalA; |
| traces_[index] = traces.begin<Instruction>(); |
| distinguishers_[index] = (++num_distinguishers_) << 48; |
| num_items_.store(index + 1); |
| return controller_.signal(0, kItemAdded); |
| } |
| |
| void AggregatedProxy::Close(ProxyImpl *coverage, zx_status_t epitaph) { |
| bindings_.CloseBinding(coverage, epitaph); |
| } |
| |
| void AggregatedProxy::Reset() { |
| Stop(); |
| Start(); |
| } |
| |
| // Private methods |
| |
| void AggregatedProxy::Start() { |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| memset(items_, 0, sizeof(items_)); |
| num_items_.store(0); |
| |
| memset(traces_, 0, sizeof(traces_)); |
| |
| memset(distinguishers_, 0, sizeof(distinguishers_)); |
| num_distinguishers_ = 0; |
| |
| ZX_ASSERT(zx::event::create(0, &controller_) == ZX_OK); |
| ZX_ASSERT(controller_.signal(0, kInIteration) == ZX_OK); |
| items_[0].handle = controller_.get(); |
| items_[0].waitfor = kItemAdded | kInIteration | kBetweenIterations | kShutdown; |
| num_items_.fetch_add(1); |
| |
| processor_ = std::thread([this]() { ProcessAll(); }); |
| } |
| |
| void AggregatedProxy::ProcessAll() { |
| bool in_iteration = true; |
| while (true) { |
| size_t num_items = num_items_.load(); |
| zx_status_t status = zx_object_wait_many(items_, num_items, ZX_TIME_INFINITE); |
| switch (status) { |
| case ZX_ERR_BAD_HANDLE: |
| case ZX_ERR_CANCELED: { |
| // One or more items are no longer valid. Compact the array into only valid items. |
| size_t orig_num_items = num_items; |
| do { |
| // Loop invariants: |
| // items_[1:i] are valid. |
| // items_[num_items:orig_num_items] have been reset. |
| for (size_t i = 1; i < num_items;) { |
| // Keep valid items. |
| if (IsValidItem(&items_[i])) { |
| i++; |
| continue; |
| } |
| // Found invalid item; replace with trailing item. |
| --num_items; |
| if (i != num_items) { |
| items_[i] = std::move(items_[num_items]); |
| traces_[i] = traces_[num_items]; |
| distinguishers_[i] = distinguishers_[num_items]; |
| } |
| // Reset the trailing item. |
| items_[num_items].handle = ZX_HANDLE_INVALID; |
| traces_[num_items] = nullptr; |
| distinguishers_[num_items] = 0; |
| } |
| // If more items were added in the meantime, loop and compact again. |
| } while (!num_items_.compare_exchange_weak(orig_num_items, num_items)); |
| break; |
| } |
| case ZX_OK: { |
| // First check the control object. |
| zx_wait_item_t *item = &items_[0]; |
| if (item->pending & kItemAdded) { |
| // New item added; interrupt all waiters and loop. |
| controller_.signal(kItemAdded, 0); |
| for (size_t i = 1; i < num_items; ++i) { |
| zx_object_signal(items_[i].handle, 0, in_iteration ? kInIteration : kBetweenIterations); |
| } |
| break; |
| |
| } else if (item->pending & kInIteration) { |
| // Beginning new iteration; let all waiters know. |
| controller_.signal(kInIteration, 0); |
| for (size_t i = 1; i < num_items; ++i) { |
| zx_object_signal(items_[i].handle, kBetweenIterations, kInIteration); |
| } |
| in_iteration = true; |
| break; |
| |
| } else if (item->pending & kBetweenIterations) { |
| // Ending iteration; let all waiters know. |
| controller_.signal(kBetweenIterations, 0); |
| pending_.store(num_items - 1); |
| for (size_t i = 1; i < num_items; ++i) { |
| zx_object_signal(items_[i].handle, kInIteration, kBetweenIterations); |
| } |
| in_iteration = false; |
| break; |
| |
| } else if (item->pending & kShutdown) { |
| // Shutting down; exit loop. |
| return; |
| } |
| // Check remaining objects. |
| for (size_t i = 1; i < num_items; ++i) { |
| zx_wait_item_t *item = &items_[i]; |
| Instruction *traces = traces_[i]; |
| uint64_t distinguisher = distinguishers_[i]; |
| |
| if (item->waitfor & item->pending & kReadableSignalA) { |
| // Expected to see kReadableSignalA, and saw it. |
| ProcessTraces(&traces[0], distinguisher); |
| item->waitfor = (item->waitfor & ~kReadableSignalA) | kReadableSignalB; |
| zx_object_signal(item->handle, kReadableSignalA, kWritableSignalA); |
| |
| } else if (item->waitfor & item->pending & kReadableSignalB) { |
| // Expected to see kReadableSignalB, and saw it. |
| ProcessTraces(&traces[kInstructionBufferLen], distinguisher); |
| item->waitfor = (item->waitfor & ~kReadableSignalB) | kReadableSignalA; |
| zx_object_signal(item->handle, kReadableSignalB, kWritableSignalB); |
| } |
| } |
| break; |
| } |
| default: { |
| FX_NOTREACHED(); |
| } |
| } |
| } |
| } |
| |
| void AggregatedProxy::ProcessTraces(Instruction *traces, uint64_t distinguisher) { |
| for (size_t i = 0; i < kInstructionBufferLen; ++i) { |
| Instruction *trace = &traces[i]; |
| if (trace->type == Instruction::kSentinel) { |
| size_t prev_pending = pending_.fetch_sub(1); |
| ZX_ASSERT(prev_pending != 0); |
| if (prev_pending == 1) { |
| sync_completion_signal(&sync_); |
| } |
| break; |
| } |
| LLVMFuzzerSetRemoteCallerPC(trace->pc ^ distinguisher); |
| switch (trace->type) { |
| case Instruction::kPcIndir: |
| __sanitizer_cov_trace_pc_indir(static_cast<uintptr_t>(trace->args[0])); |
| break; |
| case Instruction::kCmp8: |
| __sanitizer_cov_trace_cmp8(trace->args[0], trace->args[1]); |
| break; |
| case Instruction::kConstCmp8: |
| __sanitizer_cov_trace_const_cmp8(trace->args[0], trace->args[1]); |
| break; |
| case Instruction::kCmp4: |
| __sanitizer_cov_trace_cmp4(static_cast<uint32_t>(trace->args[0]), |
| static_cast<uint32_t>(trace->args[1])); |
| break; |
| case Instruction::kConstCmp4: |
| __sanitizer_cov_trace_const_cmp4(static_cast<uint32_t>(trace->args[0]), |
| static_cast<uint32_t>(trace->args[1])); |
| break; |
| case Instruction::kCmp2: |
| __sanitizer_cov_trace_cmp2(static_cast<uint16_t>(trace->args[0]), |
| static_cast<uint16_t>(trace->args[1])); |
| break; |
| case Instruction::kConstCmp2: |
| __sanitizer_cov_trace_const_cmp2(static_cast<uint16_t>(trace->args[0]), |
| static_cast<uint16_t>(trace->args[1])); |
| break; |
| case Instruction::kCmp1: |
| __sanitizer_cov_trace_cmp1(static_cast<uint8_t>(trace->args[0]), |
| static_cast<uint8_t>(trace->args[1])); |
| break; |
| case Instruction::kConstCmp1: |
| __sanitizer_cov_trace_const_cmp1(static_cast<uint8_t>(trace->args[0]), |
| static_cast<uint8_t>(trace->args[1])); |
| break; |
| case Instruction::kDiv8: |
| __sanitizer_cov_trace_div8(trace->args[0]); |
| break; |
| case Instruction::kDiv4: |
| __sanitizer_cov_trace_div4(static_cast<uint32_t>(trace->args[0])); |
| break; |
| case Instruction::kGep: |
| __sanitizer_cov_trace_gep(static_cast<uintptr_t>(trace->args[0])); |
| break; |
| default: |
| FX_NOTREACHED(); |
| } |
| } |
| } |
| |
| void AggregatedProxy::Stop() { |
| std::lock_guard<std::mutex> lock(lock_); |
| controller_.signal(0, kShutdown); |
| ZX_ASSERT(processor_.joinable()); |
| processor_.join(); |
| controller_.reset(); |
| sync_completion_signal(&sync_); |
| bindings_.CloseAll(); |
| pending_.store(0); |
| } |
| |
| // Private methods |
| |
| } // namespace fuzzing |