blob: af6b8cb5789cd2baf9a014071cd825b83dfdd888 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <iterator>
#include <memory>
#include <vector>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/task_environment.h"
#include "net/base/address_family.h"
#include "net/base/address_list.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "net/base/request_priority.h"
#include "net/dns/context_host_resolver.h"
#include "net/dns/fuzzed_host_resolver_util.h"
#include "net/dns/host_resolver.h"
#include "net/dns/host_resolver_proc.h"
#include "net/dns/public/dns_query_type.h"
#include "net/dns/public/host_resolver_source.h"
#include "net/log/net_log.h"
#include "net/log/net_log_with_source.h"
#include "net/log/test_net_log.h"
#include "net/net_buildflags.h"
namespace {
const char* kHostNames[] = {"foo", "foo.com", "a.foo.com",
"bar", "localhost", "localhost6"};
class DnsRequest {
public:
DnsRequest(net::HostResolver* host_resolver,
FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests)
: host_resolver_(host_resolver),
data_provider_(data_provider),
dns_requests_(dns_requests) {}
DnsRequest(const DnsRequest&) = delete;
DnsRequest& operator=(const DnsRequest&) = delete;
~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,
FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests) {
auto dns_request = std::make_unique<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(
FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests) {
if (dns_requests->empty())
return;
uint32_t index = data_provider->ConsumeIntegralInRange<uint32_t>(
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,
FuzzedDataProvider* data_provider,
std::vector<std::unique_ptr<DnsRequest>>* dns_requests) {
if (dns_requests->empty())
return;
uint32_t index = data_provider->ConsumeIntegralInRange<uint32_t>(
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);
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_->ConsumeIntegralInRange(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() {
net::HostResolver::ResolveHostParameters parameters;
auto* query_types_it = net::kDnsQueryTypes.cbegin();
std::advance(query_types_it, data_provider_->ConsumeIntegralInRange<size_t>(
0, net::kDnsQueryTypes.size() - 1));
parameters.dns_query_type = query_types_it->first;
parameters.initial_priority = static_cast<net::RequestPriority>(
data_provider_->ConsumeIntegralInRange<int32_t>(net::MINIMUM_PRIORITY,
net::MAXIMUM_PRIORITY));
parameters.source =
data_provider_->PickValueInArray(net::kHostResolverSources);
#if !BUILDFLAG(ENABLE_MDNS)
while (parameters.source == net::HostResolverSource::MULTICAST_DNS) {
parameters.source =
data_provider_->PickValueInArray(net::kHostResolverSources);
}
#endif // !BUILDFLAG(ENABLE_MDNS)
parameters.cache_usage =
data_provider_->ConsumeBool()
? net::HostResolver::ResolveHostParameters::CacheUsage::ALLOWED
: net::HostResolver::ResolveHostParameters::CacheUsage::DISALLOWED;
// `include_canonical_name` only allowed for address queries and only when
// the system resolver can be used.
if (net::IsAddressType(parameters.dns_query_type) &&
parameters.source != net::HostResolverSource::DNS &&
parameters.source != net::HostResolverSource::MULTICAST_DNS) {
parameters.include_canonical_name = data_provider_->ConsumeBool();
}
if (!IsParameterCombinationAllowed(parameters)) {
return net::ERR_FAILED;
}
const char* hostname = data_provider_->PickValueInArray(kHostNames);
request_ = host_resolver_->CreateRequest(
net::HostPortPair(hostname, 80), net::NetworkAnonymizationKey(),
net::NetLogWithSource(), parameters);
int rv = request_->Start(
base::BindOnce(&DnsRequest::OnCallback, base::Unretained(this)));
if (rv != net::ERR_IO_PENDING)
request_.reset();
return rv;
}
// Waits until the request is done, if it isn't done already.
void WaitUntilDone() {
CHECK(!run_loop_);
if (request_) {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
}
// Some combinations of request parameters are disallowed and expected to
// DCHECK. Returns whether or not |parameters| represents one of those cases.
static bool IsParameterCombinationAllowed(
net::HostResolver::ResolveHostParameters parameters) {
// SYSTEM requests only support address types.
if (parameters.source == net::HostResolverSource::SYSTEM &&
!net::IsAddressType(parameters.dns_query_type)) {
return false;
}
// Multiple parameters disallowed for mDNS requests.
if (parameters.source == net::HostResolverSource::MULTICAST_DNS &&
(parameters.include_canonical_name || parameters.loopback_only ||
parameters.cache_usage !=
net::HostResolver::ResolveHostParameters::CacheUsage::ALLOWED ||
parameters.dns_query_type == net::DnsQueryType::HTTPS)) {
return false;
}
return true;
}
// Cancel the request, if not already completed. Otherwise, does nothing.
void Cancel() { request_.reset(); }
raw_ptr<net::HostResolver> host_resolver_;
raw_ptr<FuzzedDataProvider> data_provider_;
raw_ptr<std::vector<std::unique_ptr<DnsRequest>>> dns_requests_;
// Non-null only while running.
std::unique_ptr<net::HostResolver::ResolveHostRequest> request_;
net::AddressList address_list_;
std::unique_ptr<base::RunLoop> run_loop_;
};
class FuzzerEnvironment {
public:
FuzzerEnvironment() {
net::SetSystemDnsResolutionTaskRunnerForTesting( // IN-TEST
base::SequencedTaskRunner::GetCurrentDefault());
}
~FuzzerEnvironment() = default;
};
void EnsureInitFuzzerEnvironment() {
static FuzzerEnvironment init_environment;
}
} // 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) {
{
FuzzedDataProvider data_provider(data, size);
EnsureInitFuzzerEnvironment();
// Including an observer; even though the recorded results aren't currently
// used, it'll ensure the netlogging code is fuzzed as well.
net::RecordingNetLogObserver net_log_observer;
net::HostResolver::ManagerOptions options;
options.max_concurrent_resolves =
data_provider.ConsumeIntegralInRange(1, 8);
options.insecure_dns_client_enabled = data_provider.ConsumeBool();
bool enable_caching = data_provider.ConsumeBool();
std::unique_ptr<net::ContextHostResolver> host_resolver =
net::CreateFuzzedContextHostResolver(options, net::NetLog::Get(),
&data_provider, enable_caching);
std::vector<std::unique_ptr<DnsRequest>> dns_requests;
bool done = false;
while (!done) {
switch (data_provider.ConsumeIntegralInRange(0, 3)) {
case 0:
// Quit on 0, or when no data is left.
done = true;
break;
case 1:
DnsRequest::CreateRequest(host_resolver.get(), &data_provider,
&dns_requests);
break;
case 2:
DnsRequest::WaitForRequestComplete(&data_provider, &dns_requests);
break;
case 3:
DnsRequest::CancelRequest(host_resolver.get(), &data_provider,
&dns_requests);
break;
}
}
}
// Clean up any pending tasks, after deleting everything.
base::RunLoop().RunUntilIdle();
return 0;
}