blob: 8e440b96a51a488c069e182f1118aced2362bb29 [file] [log] [blame]
// 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/framework/engine/process-proxy.h"
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <inspector/inspector.h>
#include "src/sys/fuzzing/common/status.h"
namespace fuzzing {
ProcessProxy::ProcessProxy(ExecutorPtr executor, uint64_t target_id, const ModulePoolPtr& pool)
: executor_(executor), target_id_(target_id), eventpair_(executor), pool_(std::move(pool)) {
FX_DCHECK(executor);
}
ProcessProxy::~ProcessProxy() {
for (auto& kv : modules_) {
auto* module_proxy = kv.first;
auto& counters = kv.second;
module_proxy->Remove(counters.data());
}
process_.kill();
}
///////////////////////////////////////////////////////////////
// Configuration methods
void ProcessProxy::AddDefaults(Options* options) {
if (!options->has_malloc_exitcode()) {
options->set_malloc_exitcode(kDefaultMallocExitcode);
}
if (!options->has_death_exitcode()) {
options->set_death_exitcode(kDefaultDeathExitcode);
}
if (!options->has_leak_exitcode()) {
options->set_leak_exitcode(kDefaultLeakExitcode);
}
if (!options->has_oom_exitcode()) {
options->set_oom_exitcode(kDefaultOomExitcode);
}
}
void ProcessProxy::Configure(const OptionsPtr& options) { options_ = options; }
zx_status_t ProcessProxy::Connect(InstrumentedProcess instrumented) {
auto* process = instrumented.mutable_process();
process_ = std::move(*process);
auto* eventpair = instrumented.mutable_eventpair();
eventpair_.Pair(std::move(*eventpair));
// Create exception channel.
zx::channel channel;
auto status = process_.create_exception_channel(0, &channel);
if (status != ZX_OK) {
// The process already crashed!
FX_LOGS(WARNING) << "Failed to create exception channel: " << zx_status_get_string(status);
result_ = FuzzResult::CRASH;
return status;
}
// If this task produces an error, then the process exited and channel was closed before or during
// the wait and/or read. |GetResult| will attempt to determine the reason using the exitcode.
auto task =
executor_->MakePromiseWaitHandle(zx::unowned_handle(channel.get()), ZX_CHANNEL_READABLE)
.and_then(
[this, channel = std::move(channel)](const zx_packet_signal_t& packet) -> ZxResult<> {
zx_exception_info_t info;
zx::exception exception;
auto status = channel.read(0, &info, exception.reset_and_get_address(),
sizeof(info), 1, nullptr, nullptr);
if (status != ZX_OK) {
return fpromise::error(status);
}
result_ = FuzzResult::CRASH;
return fpromise::ok();
})
.wrap_with(scope_);
executor_->schedule_task(std::move(task));
// Let the process know the proxy is ready to proceed.
return eventpair_.SignalPeer(0, kSync);
}
zx_status_t ProcessProxy::AddLlvmModule(LlvmModule llvm_module) {
if (!eventpair_.IsConnected()) {
FX_LOGS(WARNING) << "Failed to add module: Disconnected.";
return ZX_ERR_PEER_CLOSED;
}
SharedMemory counters;
auto* inline_8bit_counters = llvm_module.mutable_inline_8bit_counters();
if (auto status = counters.Link(std::move(*inline_8bit_counters)); status != ZX_OK) {
FX_LOGS(WARNING) << "Failed to link module: " << zx_status_get_string(status);
return status;
}
auto* module_proxy = pool_->Get(llvm_module.id(), counters.size());
module_proxy->Add(counters.data(), counters.size());
modules_[module_proxy] = std::move(counters);
return eventpair_.SignalPeer(0, kSync);
}
///////////////////////////////////////////////////////////////
// Run-related methods
ZxPromise<> ProcessProxy::Start(bool detect_leaks) {
return fpromise::make_promise([this, detect_leaks] {
return AsZxResult(eventpair_.SignalPeer(0, detect_leaks ? kStart : kStartLeakCheck));
})
.and_then(eventpair_.WaitFor(kStart))
.and_then([this](const zx_signals_t& observed) {
return AsZxResult(eventpair_.SignalSelf(kStart, 0));
})
.wrap_with(scope_);
}
zx_status_t ProcessProxy::Finish() { return eventpair_.SignalPeer(0, kFinish); }
Promise<bool, uint64_t> ProcessProxy::AwaitFinish() {
return eventpair_.WaitFor(kFinish | kFinishWithLeaks)
.and_then([this](const zx_signals_t& observed) -> ZxResult<bool> {
auto status = eventpair_.SignalSelf(kFinish | kFinishWithLeaks, 0);
if (status != ZX_OK) {
return fpromise::error(status);
}
return fpromise::ok(observed == kFinishWithLeaks);
})
.or_else([this](const zx_status_t& status) { return fpromise::error(target_id_); })
.wrap_with(scope_);
}
///////////////////////////////////////////////////////////////
// Status-related methods.
zx_status_t ProcessProxy::GetStats(ProcessStats* out) { return GetStatsForProcess(process_, out); }
ZxPromise<FuzzResult> ProcessProxy::GetResult() {
FX_DCHECK(options_);
return fpromise::make_promise([this, awaiting = ZxFuture<int64_t>()](
Context& context) mutable -> ZxResult<FuzzResult> {
if (result_ != FuzzResult::NO_ERRORS) {
return fpromise::ok(result_);
}
if (!awaiting) {
awaiting =
executor_
->MakePromiseWaitHandle(zx::unowned_handle(process_.get()),
ZX_PROCESS_TERMINATED)
.and_then([this](const zx_packet_signal_t& packet) -> ZxResult<int64_t> {
FX_DCHECK(packet.observed & ZX_PROCESS_TERMINATED);
zx_info_process_t info;
auto status = process_.get_info(ZX_INFO_PROCESS, &info, sizeof(info),
nullptr, nullptr);
if (status != ZX_OK) {
return fpromise::error(status);
}
FX_CHECK(info.flags & ZX_INFO_PROCESS_FLAG_EXITED);
return fpromise::ok(info.return_code);
});
}
if (!awaiting(context)) {
return fpromise::pending();
}
if (awaiting.is_error()) {
return fpromise::error(awaiting.error());
}
// Set the result, unless it was already set.
if (result_ == FuzzResult::NO_ERRORS) {
auto return_code = awaiting.value();
if (return_code == options_->malloc_exitcode()) {
result_ = FuzzResult::BAD_MALLOC;
} else if (return_code == options_->death_exitcode()) {
result_ = FuzzResult::DEATH;
} else if (return_code == options_->leak_exitcode()) {
result_ = FuzzResult::LEAK;
} else if (return_code == options_->oom_exitcode()) {
result_ = FuzzResult::OOM;
} else if (return_code != 0) {
result_ = FuzzResult::EXIT;
}
}
return fpromise::ok(result_);
})
.wrap_with(scope_);
}
size_t ProcessProxy::Dump(void* buf, size_t size) {
FX_DCHECK(buf && size);
auto* out = fmemopen(buf, size, "r+");
char* str = reinterpret_cast<char*>(buf);
if (!out) {
FX_LOGS(ERROR) << "Cannot dump threads; fmemopen failed (errno=" << errno << ").";
str[0] = 0;
return 0;
}
inspector_print_debug_info_for_all_threads(out, process_.get());
fclose(out);
str[size - 1] = 0;
return strlen(str);
}
} // namespace fuzzing