| // 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/common/async-socket.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/zx/socket.h> | |
| #include <zircon/status.h> | |
| namespace fuzzing { | |
| namespace { | |
| struct TransferParams { | |
| ExecutorPtr executor; | |
| const char* label; | |
| zx::socket socket; | |
| Input input; | |
| zx_signals_t ready; | |
| zx_signals_t done; | |
| }; | |
| template <typename Transfer> | |
| ZxPromise<Input> AsyncSocketTransfer(TransferParams params, Transfer transfer) { | |
| FX_DCHECK(params.executor); | |
| if (params.input.size() == 0) { | |
| return fpromise::make_promise([input = std::move(params.input)]() mutable -> ZxResult<Input> { | |
| return fpromise::ok(std::move(input)); | |
| }); | |
| } | |
| return fpromise::make_promise([executor = params.executor, label = params.label, | |
| socket = std::move(params.socket), input = std::move(params.input), | |
| ready = params.ready, done = params.done, | |
| transfer = std::move(transfer), offset = size_t(0), | |
| awaiting = | |
| ZxFuture<>()](Context& context) mutable -> ZxResult<Input> { | |
| while (true) { | |
| size_t actual = 0; | |
| auto status = transfer(socket, input.data() + offset, input.size() - offset, &actual); | |
| if (status == ZX_OK) { | |
| offset += actual; | |
| FX_DCHECK(offset <= input.size()); | |
| } else if (status != ZX_ERR_SHOULD_WAIT) { | |
| FX_LOGS(WARNING) << "Failed to " << label << " socket: " << zx_status_get_string(status); | |
| return fpromise::error(status); | |
| } | |
| if (offset == input.size()) { | |
| return fpromise::ok(std::move(input)); | |
| } | |
| if (!awaiting) { | |
| awaiting = executor->MakePromiseWaitHandle(zx::unowned_handle(socket.get()), ready | done) | |
| .and_then([ready](const zx_packet_signal_t& packet) -> ZxResult<> { | |
| if (packet.observed & ready) { | |
| return fpromise::ok(); | |
| } | |
| return fpromise::error(ZX_ERR_PEER_CLOSED); | |
| }); | |
| } | |
| if (!awaiting(context)) { | |
| return fpromise::pending(); | |
| } | |
| if (awaiting.is_error()) { | |
| auto status = awaiting.error(); | |
| FX_LOGS(WARNING) << "Failed to " << label << " socket: " << zx_status_get_string(status); | |
| return fpromise::error(status); | |
| } | |
| awaiting = nullptr; | |
| } | |
| }); | |
| } | |
| } // namespace | |
| ZxPromise<Input> AsyncSocketRead(const ExecutorPtr& executor, FidlInput fidl_input) { | |
| TransferParams params = { | |
| .executor = executor, | |
| .label = "read from", | |
| .socket = std::move(fidl_input.socket), | |
| .input = Input(fidl_input.size), | |
| .ready = ZX_SOCKET_READABLE, | |
| .done = ZX_SOCKET_PEER_WRITE_DISABLED | ZX_SOCKET_PEER_CLOSED, | |
| }; | |
| return AsyncSocketTransfer( | |
| std::move(params), [](const zx::socket& socket, uint8_t* buf, size_t len, size_t* actual) { | |
| return socket.read(0, buf, len, actual); | |
| }); | |
| } | |
| ZxPromise<Artifact> AsyncSocketRead(const ExecutorPtr& executor, FidlArtifact fidl_artifact) { | |
| if (fidl_artifact.IsEmpty()) { | |
| return fpromise::make_promise([status = fidl_artifact.error()]() -> ZxResult<Artifact> { | |
| return fpromise::ok(Artifact()); | |
| }); | |
| } | |
| if (fidl_artifact.has_error()) { | |
| return fpromise::make_promise([status = fidl_artifact.error()]() -> ZxResult<Artifact> { | |
| return fpromise::error(status); | |
| }); | |
| } | |
| if (!fidl_artifact.has_result()) { | |
| return fpromise::make_promise([]() -> ZxResult<Artifact> { | |
| FX_LOGS(WARNING) << "Received invalid artifact: missing `result`"; | |
| return fpromise::error(ZX_ERR_INTERNAL); | |
| }); | |
| } | |
| if (!fidl_artifact.has_input()) { | |
| return fpromise::make_promise([fuzz_result = fidl_artifact.result()]() -> ZxResult<Artifact> { | |
| return fpromise::ok(Artifact(fuzz_result)); | |
| }); | |
| } | |
| auto* fidl_input = fidl_artifact.mutable_input(); | |
| return AsyncSocketRead(executor, std::move(*fidl_input)) | |
| .and_then([fuzz_result = fidl_artifact.result()](Input& input) -> ZxResult<Artifact> { | |
| return fpromise::ok(Artifact(fuzz_result, std::move(input))); | |
| }); | |
| } | |
| FidlInput AsyncSocketWrite(const ExecutorPtr& executor, const Input& input) { | |
| FidlInput fidl_input; | |
| fidl_input.size = input.size(); | |
| zx::socket socket; | |
| auto status = zx::socket::create(ZX_SOCKET_STREAM, &socket, &fidl_input.socket); | |
| FX_DCHECK(status == ZX_OK) << zx_status_get_string(status); | |
| fidl_input.socket.set_disposition(ZX_SOCKET_DISPOSITION_WRITE_DISABLED, 0); | |
| FX_DCHECK(status == ZX_OK) << zx_status_get_string(status); | |
| TransferParams params = { | |
| .executor = executor, | |
| .label = "write to", | |
| .socket = std::move(socket), | |
| .input = input.Duplicate(), | |
| .ready = ZX_SOCKET_WRITABLE, | |
| .done = ZX_SOCKET_PEER_CLOSED, | |
| }; | |
| auto task = AsyncSocketTransfer(std::move(params), [](const zx::socket& socket, uint8_t* buf, | |
| size_t len, size_t* actual) { | |
| return socket.write(0, buf, len, actual); | |
| }).and_then([](Input& input) -> ZxResult<> { return fpromise::ok(); }); | |
| executor->schedule_task(std::move(task)); | |
| return fidl_input; | |
| } | |
| FidlArtifact AsyncSocketWrite(const ExecutorPtr& executor, const Artifact& artifact) { | |
| if (artifact.is_empty()) { | |
| return FidlArtifact(); | |
| } | |
| FidlArtifact fidl_artifact; | |
| fidl_artifact.set_result(artifact.fuzz_result()); | |
| if (artifact.has_input()) { | |
| fidl_artifact.set_input(AsyncSocketWrite(executor, artifact.input())); | |
| } | |
| return fidl_artifact; | |
| } | |
| } // namespace fuzzing |