blob: 1f877b89942db8fcd669aa548b7d06343eafe6f5 [file] [log] [blame]
// Copyright 2021 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/testing/target.h"
#include <lib/fdio/spawn.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/exception.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls/exception.h>
namespace fuzzing {
TestTarget::TestTarget(ExecutorPtr executor) : executor_(std::move(executor)) {}
TestTarget::~TestTarget() { Reset(); }
zx::process TestTarget::Launch() {
Reset();
// First, create the channel between this object and the new process.
zx::channel remote;
auto status = zx::channel::create(0, &local_, &remote);
FX_DCHECK(status == ZX_OK) << zx_status_get_string(status);
// Spawn the new process in the new job.
const char* argv[2] = {"/pkg/bin/component_fuzzing_framework_test_target", nullptr};
fdio_spawn_action_t actions[] = {
{
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h =
{
.id = PA_HND(PA_USER0, 0),
.handle = remote.release(),
},
},
};
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
status = fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, argv[0], argv, nullptr,
sizeof(actions) / sizeof(actions[0]), actions,
process_.reset_and_get_address(), err_msg);
FX_DCHECK(status == ZX_OK) << err_msg;
// Install a process-debug exception handler. This will receive new exceptions before the process
// exception handler that we want to test, so on the first pass simply set the "second-chance"
// strategy, and on receiving them again, simply kill the process to suppress further handling.
zx::channel channel;
status = process_.create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &channel);
FX_DCHECK(status == ZX_OK) << zx_status_get_string(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 =
fpromise::make_promise([this, channel = std::move(channel),
crash = ZxFuture<zx_packet_signal_t>()](
Context& context) mutable -> Result<> {
while (true) {
if (!crash) {
crash = executor_->MakePromiseWaitHandle(zx::unowned_handle(channel.get()),
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED);
}
if (!crash(context)) {
return fpromise::pending();
}
if (crash.is_error()) {
return fpromise::ok();
}
auto packet = crash.take_value();
if ((packet.observed & ZX_CHANNEL_READABLE) == 0) {
return fpromise::ok();
}
zx::exception exception;
zx_exception_info_t info;
uint32_t strategy;
if (channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1, nullptr,
nullptr) != ZX_OK ||
!exception.is_valid() ||
exception.get_property(ZX_PROP_EXCEPTION_STRATEGY, &strategy, sizeof(strategy)) !=
ZX_OK) {
continue;
}
if (strategy == ZX_EXCEPTION_STRATEGY_SECOND_CHANCE) {
process_.kill();
} else {
strategy = ZX_EXCEPTION_STRATEGY_SECOND_CHANCE;
exception.set_property(ZX_PROP_EXCEPTION_STRATEGY, &strategy, sizeof(strategy));
}
}
}).wrap_with(scope_);
FX_DCHECK(executor_);
executor_->schedule_task(std::move(task));
zx_info_handle_basic_t info;
status = process_.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
FX_CHECK(status == ZX_OK) << zx_status_get_string(status);
id_ = info.koid;
// Return a copy of the process.
zx::process process;
status = process_.duplicate(ZX_RIGHT_SAME_RIGHTS, &process);
FX_CHECK(status == ZX_OK) << zx_status_get_string(status);
return process;
}
ZxPromise<> TestTarget::Crash() {
// Resetting the channel will trigger an |FX_CHECK| in the target process. Tests that use this
// method must suppress fatal log message being treated as test failures.
return fpromise::make_promise([this]() -> ZxResult<> {
local_.reset();
return fpromise::ok();
})
.and_then(AwaitTermination())
.wrap_with(scope_);
}
ZxPromise<> TestTarget::Exit(int32_t exitcode) {
return fpromise::make_promise([this, exitcode]() -> ZxResult<> {
return AsZxResult(local_.write(0, &exitcode, sizeof(exitcode), nullptr, 0));
})
.and_then(AwaitTermination())
.wrap_with(scope_);
}
ZxPromise<> TestTarget::AwaitTermination() {
return executor_->MakePromiseWaitHandle(zx::unowned_handle(process_.get()), ZX_PROCESS_TERMINATED)
.and_then([](const zx_packet_signal_t& packet) { return fpromise::ok(); })
.wrap_with(scope_);
}
void TestTarget::Reset() {
process_.kill();
local_.reset();
process_.reset();
}
} // namespace fuzzing