| // 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); |
| } |