blob: 95eeb965d844cb8db596e34d8a153c5765d025dc [file] [log] [blame]
// 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 <memory>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/test/fuzzed_data_provider.h"
#include "base/test/scoped_task_environment.h"
#include "net/base/address_family.h"
#include "net/base/address_list.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/dns/fuzzed_host_resolver.h"
#include "net/dns/host_resolver.h"
#include "net/log/net_log_with_source.h"
#include "net/log/test_net_log.h"
#include "starboard/types.h"
namespace {
const char* kHostNames[] = {"foo", "foo.com", "a.foo.com",
"bar", "localhost", "localhost6"};
net::AddressFamily kAddressFamilies[] = {
net::ADDRESS_FAMILY_UNSPECIFIED, net::ADDRESS_FAMILY_IPV4,
net::ADDRESS_FAMILY_IPV6,
};
class DnsRequest {
public:
DnsRequest(net::HostResolver* host_resolver,
base::FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests)
: host_resolver_(host_resolver),
data_provider_(data_provider),
dns_requests_(dns_requests),
is_running_(false) {}
~DnsRequest() = default;
// Creates and starts a DNS request using fuzzed parameters. If the request
// doesn't complete synchronously, adds it to |dns_requests|.
static void CreateRequest(
net::HostResolver* host_resolver,
base::FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests) {
std::unique_ptr<DnsRequest> dns_request(
new DnsRequest(host_resolver, data_provider, dns_requests));
if (dns_request->Start() == net::ERR_IO_PENDING)
dns_requests->push_back(std::move(dns_request));
}
// If |dns_requests| is non-empty, waits for a randomly chosen one of the
// requests to complete and removes it from |dns_requests|.
static void WaitForRequestComplete(
base::FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests) {
if (dns_requests->empty())
return;
uint32_t index =
data_provider->ConsumeUint32InRange(0, dns_requests->size() - 1);
// Remove the request from the list before waiting on it - this prevents one
// of the other callbacks from deleting the callback being waited on.
std::unique_ptr<DnsRequest> request = std::move((*dns_requests)[index]);
dns_requests->erase(dns_requests->begin() + index);
request->WaitUntilDone();
}
// If |dns_requests| is non-empty, attempts to cancel a randomly chosen one of
// them and removes it from |dns_requests|. If the one it picks is already
// complete, just removes it from the list.
static void CancelRequest(
net::HostResolver* host_resolver,
base::FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests) {
if (dns_requests->empty())
return;
uint32_t index =
data_provider->ConsumeUint32InRange(0, dns_requests->size() - 1);
auto request = dns_requests->begin() + index;
(*request)->Cancel();
dns_requests->erase(request);
}
private:
void OnCallback(int result) {
CHECK_NE(net::ERR_IO_PENDING, result);
is_running_ = false;
request_.reset();
// Remove |this| from |dns_requests| and take ownership of it, if it wasn't
// already removed from the vector. It may have been removed if this is in a
// WaitForRequest call, in which case, do nothing.
std::unique_ptr<DnsRequest> self;
for (auto request = dns_requests_->begin(); request != dns_requests_->end();
++request) {
if (request->get() != this)
continue;
self = std::move(*request);
dns_requests_->erase(request);
break;
}
while (true) {
bool done = false;
switch (data_provider_->ConsumeInt32InRange(0, 2)) {
case 0:
// Quit on 0, or when no data is left.
done = true;
break;
case 1:
CreateRequest(host_resolver_, data_provider_, dns_requests_);
break;
case 2:
CancelRequest(host_resolver_, data_provider_, dns_requests_);
break;
}
if (done)
break;
}
if (run_loop_)
run_loop_->Quit();
}
// Starts the DNS request, using a fuzzed set of parameters.
int Start() {
const char* hostname = data_provider_->PickValueInArray(kHostNames);
net::HostResolver::RequestInfo info(net::HostPortPair(hostname, 80));
info.set_address_family(data_provider_->PickValueInArray(kAddressFamilies));
if (data_provider_->ConsumeBool())
info.set_host_resolver_flags(net::HOST_RESOLVER_CANONNAME);
net::RequestPriority priority =
static_cast<net::RequestPriority>(data_provider_->ConsumeInt32InRange(
net::MINIMUM_PRIORITY, net::MAXIMUM_PRIORITY));
// Decide if should be a cache-only resolution.
if (data_provider_->ConsumeBool()) {
return host_resolver_->ResolveFromCache(info, &address_list_,
net::NetLogWithSource());
}
info.set_allow_cached_response(data_provider_->ConsumeBool());
int rv = host_resolver_->Resolve(
info, priority, &address_list_,
base::Bind(&DnsRequest::OnCallback, base::Unretained(this)), &request_,
net::NetLogWithSource());
if (rv == net::ERR_IO_PENDING)
is_running_ = true;
return rv;
}
// Waits until the request is done, if it isn't done already.
void WaitUntilDone() {
CHECK(!run_loop_);
if (is_running_) {
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
run_loop_.reset();
}
}
// Cancel the request, if not already completed. Otherwise, does nothing.
void Cancel() {
request_.reset();
is_running_ = false;
}
net::HostResolver* host_resolver_;
base::FuzzedDataProvider* data_provider_;
std::vector<std::unique_ptr<DnsRequest>>* dns_requests_;
std::unique_ptr<net::HostResolver::Request> request_;
net::AddressList address_list_;
bool is_running_;
std::unique_ptr<base::RunLoop> run_loop_;
DISALLOW_COPY_AND_ASSIGN(DnsRequest);
};
} // namespace
// Fuzzer for HostResolverImpl. Fuzzes using both the system resolver and
// built-in DNS client paths.
//
// TODO(mmenke): Add coverage for things this does not cover:
// * Out of order completion, particularly for the platform resolver path.
// * Simulate network changes, including both enabling and disabling the
// async resolver while lookups are active as a result of the change.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
{
base::FuzzedDataProvider data_provider(data, size);
net::TestNetLog net_log;
net::HostResolver::Options options;
options.max_concurrent_resolves = data_provider.ConsumeUint32InRange(1, 8);
options.enable_caching = data_provider.ConsumeBool();
net::FuzzedHostResolver host_resolver(options, &net_log, &data_provider);
host_resolver.SetDnsClientEnabled(data_provider.ConsumeBool());
std::vector<std::unique_ptr<DnsRequest>> dns_requests;
bool done = false;
while (!done) {
switch (data_provider.ConsumeInt32InRange(0, 3)) {
case 0:
// Quit on 0, or when no data is left.
done = true;
break;
case 1:
DnsRequest::CreateRequest(&host_resolver, &data_provider,
&dns_requests);
break;
case 2:
DnsRequest::WaitForRequestComplete(&data_provider, &dns_requests);
break;
case 3:
DnsRequest::CancelRequest(&host_resolver, &data_provider,
&dns_requests);
break;
}
}
}
// Clean up any pending tasks, after deleting everything.
base::RunLoop().RunUntilIdle();
return 0;
}