// Copyright 2016 The Chromium 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 "net/socket/fuzzed_socket.h" #include #include "base/bind.h" #include "base/location.h" #include "base/logging.h" #include "base/test/fuzzed_data_provider.h" #include "base/threading/thread_task_runner_handle.h" #include "net/base/io_buffer.h" #include "net/log/net_log_source_type.h" #include "net/traffic_annotation/network_traffic_annotation.h" namespace net { namespace { const int kMaxAsyncReadsAndWrites = 1000; // Some of the socket errors that can be returned by normal socket connection // attempts. const Error kConnectErrors[] = { ERR_CONNECTION_RESET, ERR_CONNECTION_CLOSED, ERR_FAILED, ERR_CONNECTION_TIMED_OUT, ERR_ACCESS_DENIED, ERR_CONNECTION_REFUSED, ERR_ADDRESS_UNREACHABLE}; // Some of the socket errors that can be returned by normal socket reads / // writes. The first one is returned when no more input data remains, so it's // one of the most common ones. const Error kReadWriteErrors[] = {ERR_CONNECTION_CLOSED, ERR_FAILED, ERR_TIMED_OUT, ERR_CONNECTION_RESET}; } // namespace FuzzedSocket::FuzzedSocket(base::FuzzedDataProvider* data_provider, net::NetLog* net_log) : data_provider_(data_provider), net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::SOCKET)), remote_address_(IPEndPoint(IPAddress::IPv4Localhost(), 80)), weak_factory_(this) {} FuzzedSocket::~FuzzedSocket() = default; int FuzzedSocket::Read(IOBuffer* buf, int buf_len, CompletionOnceCallback callback) { DCHECK(!connect_pending_); DCHECK(!read_pending_); bool sync; int result; if (net_error_ != OK) { // If an error has already been generated, use it to determine what to do. result = net_error_; sync = !error_pending_; } else { // Otherwise, use |data_provider_|. Always consume a bool, even when // ForceSync() is true, to behave more consistently against input mutations. sync = data_provider_->ConsumeBool() || ForceSync(); num_async_reads_and_writes_ += static_cast(!sync); std::string data = data_provider_->ConsumeRandomLengthString(buf_len); result = data.size(); if (result > 0) { std::copy(data.data(), data.data() + result, buf->data()); } else { result = ConsumeReadWriteErrorFromData(); net_error_ = result; if (!sync) error_pending_ = true; } } // Graceful close of a socket returns OK, at least in theory. This doesn't // perfectly reflect real socket behavior, but close enough. if (result == ERR_CONNECTION_CLOSED) result = 0; if (sync) { if (result > 0) total_bytes_read_ += result; return result; } read_pending_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&FuzzedSocket::OnReadComplete, weak_factory_.GetWeakPtr(), std::move(callback), result)); return ERR_IO_PENDING; } int FuzzedSocket::Write( IOBuffer* buf, int buf_len, CompletionOnceCallback callback, const NetworkTrafficAnnotationTag& /* traffic_annotation */) { DCHECK(!connect_pending_); DCHECK(!write_pending_); bool sync; int result; if (net_error_ != OK) { // If an error has already been generated, use it to determine what to do. result = net_error_; sync = !error_pending_; } else { // Otherwise, use |data_provider_|. Always consume a bool, even when // ForceSync() is true, to behave more consistently against input mutations. sync = data_provider_->ConsumeBool() || ForceSync(); num_async_reads_and_writes_ += static_cast(!sync); result = data_provider_->ConsumeUint8(); if (result > buf_len) result = buf_len; if (result == 0) { net_error_ = ConsumeReadWriteErrorFromData(); result = net_error_; if (!sync) error_pending_ = true; } } if (sync) { if (result > 0) total_bytes_written_ += result; return result; } write_pending_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&FuzzedSocket::OnWriteComplete, weak_factory_.GetWeakPtr(), std::move(callback), result)); return ERR_IO_PENDING; } int FuzzedSocket::SetReceiveBufferSize(int32_t size) { return OK; } int FuzzedSocket::SetSendBufferSize(int32_t size) { return OK; } int FuzzedSocket::Bind(const net::IPEndPoint& local_addr) { NOTREACHED(); return ERR_NOT_IMPLEMENTED; } int FuzzedSocket::Connect(CompletionOnceCallback callback) { // Sockets can normally be reused, but don't support it here. DCHECK_NE(net_error_, OK); DCHECK(!connect_pending_); DCHECK(!read_pending_); DCHECK(!write_pending_); DCHECK(!error_pending_); DCHECK(!total_bytes_read_); DCHECK(!total_bytes_written_); bool sync = true; Error result = OK; if (fuzz_connect_result_) { // Decide if sync or async. Use async, if no data is left. sync = data_provider_->ConsumeBool(); // Decide if the connect succeeds or not, and if so, pick an error code. if (data_provider_->ConsumeBool()) result = data_provider_->PickValueInArray(kConnectErrors); } if (sync) { net_error_ = result; return result; } connect_pending_ = true; if (result != OK) error_pending_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&FuzzedSocket::OnConnectComplete, weak_factory_.GetWeakPtr(), std::move(callback), result)); return ERR_IO_PENDING; } void FuzzedSocket::Disconnect() { net_error_ = ERR_CONNECTION_CLOSED; weak_factory_.InvalidateWeakPtrs(); connect_pending_ = false; read_pending_ = false; write_pending_ = false; error_pending_ = false; } bool FuzzedSocket::IsConnected() const { return net_error_ == OK && !error_pending_; } bool FuzzedSocket::IsConnectedAndIdle() const { return IsConnected(); } int FuzzedSocket::GetPeerAddress(IPEndPoint* address) const { if (!IsConnected()) return ERR_SOCKET_NOT_CONNECTED; *address = remote_address_; return OK; } int FuzzedSocket::GetLocalAddress(IPEndPoint* address) const { if (!IsConnected()) return ERR_SOCKET_NOT_CONNECTED; *address = IPEndPoint(IPAddress(127, 0, 0, 1), 43434); return OK; } const NetLogWithSource& FuzzedSocket::NetLog() const { return net_log_; } bool FuzzedSocket::WasEverUsed() const { return total_bytes_written_ != 0 || total_bytes_read_ != 0; } void FuzzedSocket::EnableTCPFastOpenIfSupported() {} bool FuzzedSocket::WasAlpnNegotiated() const { return false; } NextProto FuzzedSocket::GetNegotiatedProtocol() const { return kProtoUnknown; } bool FuzzedSocket::GetSSLInfo(SSLInfo* ssl_info) { return false; } void FuzzedSocket::GetConnectionAttempts(ConnectionAttempts* out) const { out->clear(); } void FuzzedSocket::ClearConnectionAttempts() {} void FuzzedSocket::AddConnectionAttempts(const ConnectionAttempts& attempts) {} int64_t FuzzedSocket::GetTotalReceivedBytes() const { return total_bytes_read_; } void FuzzedSocket::ApplySocketTag(const net::SocketTag& tag) {} Error FuzzedSocket::ConsumeReadWriteErrorFromData() { return data_provider_->PickValueInArray(kReadWriteErrors); } void FuzzedSocket::OnReadComplete(CompletionOnceCallback callback, int result) { CHECK(read_pending_); read_pending_ = false; if (result <= 0) { error_pending_ = false; } else { total_bytes_read_ += result; } std::move(callback).Run(result); } void FuzzedSocket::OnWriteComplete(CompletionOnceCallback callback, int result) { CHECK(write_pending_); write_pending_ = false; if (result <= 0) { error_pending_ = false; } else { total_bytes_written_ += result; } std::move(callback).Run(result); } void FuzzedSocket::OnConnectComplete(CompletionOnceCallback callback, int result) { CHECK(connect_pending_); connect_pending_ = false; if (result < 0) error_pending_ = false; net_error_ = result; std::move(callback).Run(result); } bool FuzzedSocket::ForceSync() const { return (num_async_reads_and_writes_ >= kMaxAsyncReadsAndWrites); } } // namespace net