blob: fa6ad4b33b32ffc17dca622b762a28240ab822fb [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 <string>
#include "base/memory/singleton.h"
#include "base/run_loop.h"
#include "base/test/fuzzed_data_provider.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/http/http_request_headers.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/data_protocol_handler.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
namespace {
const size_t kMaxLengthForFuzzedRange = 32;
} // namespace
// This class tests creating and reading to completion a URLRequest with fuzzed
// input. The fuzzer provides a data: URL and optionally generates custom Range
// headers. The amount of data read in each Read call is also fuzzed, as is
// the size of the IOBuffer to read data into.
class URLRequestDataJobFuzzerHarness : public net::URLRequest::Delegate {
public:
URLRequestDataJobFuzzerHarness()
: task_runner_(base::ThreadTaskRunnerHandle::Get()), context_(true) {
job_factory_.SetProtocolHandler(
"data", std::make_unique<net::DataProtocolHandler>());
context_.set_job_factory(&job_factory_);
context_.Init();
}
static URLRequestDataJobFuzzerHarness* GetInstance() {
return base::Singleton<URLRequestDataJobFuzzerHarness>::get();
}
int CreateAndReadFromDataURLRequest(const uint8_t* data, size_t size) {
base::FuzzedDataProvider provider(data, size);
read_lengths_.clear();
// Allocate an IOBuffer with fuzzed size.
int buf_size = provider.ConsumeUint32InRange(1, 127); // 7 bits.
buf_ = base::MakeRefCounted<net::IOBufferWithSize>(buf_size);
// Generate a range header, and a bool determining whether to use it.
// Generate the header regardless of the bool value to keep the data URL and
// header in consistent byte addresses so the fuzzer doesn't have to work as
// hard.
bool use_range = provider.ConsumeBool();
std::string range(provider.ConsumeBytes(kMaxLengthForFuzzedRange));
// Generate a sequence of reads sufficient to read the entire data URL,
// capping it at 20000 reads, to avoid hangs. Once the limit is reached,
// all subsequent reads will be 32k.
size_t simulated_bytes_read = 0;
while (simulated_bytes_read < provider.remaining_bytes() &&
read_lengths_.size() < 20000u) {
size_t read_length = provider.ConsumeUint32InRange(1, buf_size);
read_lengths_.push_back(read_length);
simulated_bytes_read += read_length;
}
// The data URL is the rest of the fuzzed data with "data:" prepended, to
// ensure that if it's a URL, it's a data URL. If the URL is invalid just
// use a test variant, so the fuzzer has a chance to execute something.
std::string data_url_string =
std::string("data:") + provider.ConsumeRemainingBytes();
GURL data_url(data_url_string);
if (!data_url.is_valid())
data_url = GURL("data:text/html;charset=utf-8,<p>test</p>");
// Create a URLRequest with the given data URL and start reading
// from it.
std::unique_ptr<net::URLRequest> request = context_.CreateRequest(
data_url, net::DEFAULT_PRIORITY, this, TRAFFIC_ANNOTATION_FOR_TESTS);
if (use_range) {
if (!net::HttpUtil::IsValidHeaderValue(range))
range = "bytes=3-";
request->SetExtraRequestHeaderByName("Range", range, true);
}
// Block the thread while the request is read.
base::RunLoop read_loop;
read_loop_ = &read_loop;
request->Start();
read_loop.Run();
read_loop_ = nullptr;
return 0;
}
void QuitLoop() {
DCHECK(read_loop_);
task_runner_->PostTask(FROM_HERE, read_loop_->QuitClosure());
}
void ReadFromRequest(net::URLRequest* request) {
int bytes_read = 0;
do {
size_t read_size = 32 * 1024;
// If possible, pop the next read size.
if (read_lengths_.size() > 0) {
read_size = read_lengths_.back();
read_lengths_.pop_back();
}
if (read_size > static_cast<size_t>(buf_->size()))
buf_ = base::MakeRefCounted<net::IOBufferWithSize>(read_size);
bytes_read = request->Read(buf_.get(), read_size);
} while (bytes_read > 0);
if (bytes_read != net::ERR_IO_PENDING)
QuitLoop();
}
// net::URLRequest::Delegate:
void OnReceivedRedirect(net::URLRequest* request,
const net::RedirectInfo& redirect_info,
bool* defer_redirect) override {}
void OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) override {}
void OnCertificateRequested(
net::URLRequest* request,
net::SSLCertRequestInfo* cert_request_info) override {}
void OnSSLCertificateError(net::URLRequest* request,
const net::SSLInfo& ssl_info,
bool fatal) override {}
void OnResponseStarted(net::URLRequest* request, int net_error) override {
DCHECK(buf_.get());
DCHECK(read_loop_);
DCHECK_NE(net::ERR_IO_PENDING, net_error);
if (net_error == net::OK) {
ReadFromRequest(request);
} else {
QuitLoop();
}
}
void OnReadCompleted(net::URLRequest* request, int bytes_read) override {
DCHECK_NE(net::ERR_IO_PENDING, bytes_read);
DCHECK(buf_.get());
DCHECK(read_loop_);
if (bytes_read > 0) {
ReadFromRequest(request);
} else {
QuitLoop();
}
}
private:
friend struct base::DefaultSingletonTraits<URLRequestDataJobFuzzerHarness>;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
net::TestURLRequestContext context_;
net::URLRequestJobFactoryImpl job_factory_;
std::vector<size_t> read_lengths_;
scoped_refptr<net::IOBufferWithSize> buf_;
base::RunLoop* read_loop_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(URLRequestDataJobFuzzerHarness);
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Using a static singleton test harness lets the test run ~3-4x faster.
return URLRequestDataJobFuzzerHarness::GetInstance()
->CreateAndReadFromDataURLRequest(data, size);
}