| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| |
| #include <utility> |
| |
| #include "base/i18n/icu_util.h" |
| #include "base/no_destructor.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/thread.h" |
| #include "content/browser/blob_storage/chrome_blob_storage_context.h" // [nogncheck] |
| #include "content/browser/cache_storage/cache_storage_control_wrapper.h" // [nogncheck] |
| #include "content/browser/code_cache/generated_code_cache_context.h" // [nogncheck] |
| #include "content/browser/renderer_host/code_cache_host_impl.h" // [nogncheck] |
| #include "content/browser/storage_partition_impl.h" // [nogncheck] |
| #include "content/browser/storage_partition_impl_map.h" // [nogncheck] |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/test/fuzzer/code_cache_host_mojolpm_fuzzer.pb.h" |
| #include "content/test/fuzzer/mojolpm_fuzzer_support.h" |
| #include "storage/browser/quota/quota_manager.h" |
| #include "storage/browser/quota/special_storage_policy.h" |
| #include "storage/browser/test/mock_special_storage_policy.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/mojom/loader/code_cache.mojom-mojolpm.h" |
| #include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h" |
| #include "url/origin.h" |
| |
| using url::Origin; |
| |
| const char* kCmdline[] = {"code_cache_host_mojolpm_fuzzer", nullptr}; |
| |
| content::mojolpm::FuzzerEnvironment& GetEnvironment() { |
| static base::NoDestructor< |
| content::mojolpm::FuzzerEnvironmentWithTaskEnvironment> |
| environment(1, kCmdline); |
| return *environment; |
| } |
| |
| scoped_refptr<base::SequencedTaskRunner> GetFuzzerTaskRunner() { |
| return GetEnvironment().fuzzer_task_runner(); |
| } |
| |
| // Per-testcase state needed to run the interface being tested. |
| // |
| // The lifetime of this is scoped to a single testcase, and it is created and |
| // destroyed from the fuzzer sequence. |
| // |
| // For CodeCacheHost, this needs the basic common Browser process state provided |
| // by TestBrowserContext, and to set up the cache storage that will provide the |
| // storage backing for the code cache. |
| // |
| // Since the Browser process will host one CodeCacheHostImpl per |
| // RenderProcessHost, we emulate this by allowing the fuzzer to create (and |
| // destroy) multiple CodeCacheHostImpl instances. |
| class CodeCacheHostTestcase |
| : public mojolpm::Testcase< |
| content::fuzzing::code_cache_host::proto::Testcase, |
| content::fuzzing::code_cache_host::proto::Action> { |
| public: |
| using ProtoTestcase = content::fuzzing::code_cache_host::proto::Testcase; |
| using ProtoAction = content::fuzzing::code_cache_host::proto::Action; |
| |
| explicit CodeCacheHostTestcase(const ProtoTestcase& testcase); |
| |
| void SetUp(base::OnceClosure done_closure) override; |
| void TearDown(base::OnceClosure done_closure) override; |
| |
| void RunAction(const ProtoAction& action, |
| base::OnceClosure done_closure) override; |
| |
| private: |
| void SetUpOnUIThread(base::OnceClosure done_closure); |
| void SetUpOnFuzzerThread(base::OnceClosure done_closure); |
| |
| void TearDownOnUIThread(base::OnceClosure done_closure); |
| void TearDownOnFuzzerThread(base::OnceClosure done_closure); |
| |
| // Used by AddCodeCacheHost to create and bind CodeCacheHostImpl on the code |
| // cache thread. |
| void AddCodeCacheHostImpl( |
| uint32_t id, |
| int renderer_id, |
| const net::NetworkIsolationKey& key, |
| const blink::StorageKey& storage_key, |
| mojo::PendingReceiver<::blink::mojom::CodeCacheHost>&& receiver); |
| |
| // Create and bind a new instance for fuzzing. This ensures that the new |
| // instance has been created and bound on the correct sequence before invoking |
| // `done_closure`. |
| void AddCodeCacheHost( |
| uint32_t id, |
| int renderer_id, |
| content::fuzzing::code_cache_host::proto::NewCodeCacheHostAction::OriginId |
| origin_id, |
| base::OnceClosure done_closure); |
| |
| // This set of origins should cover all of the origin types which have special |
| // handling in CodeCacheHostImpl, and give us two distinct "normal" origins, |
| // which should be enough to exercise all of the code. |
| const Origin origin_a_; |
| const Origin origin_b_; |
| const Origin origin_opaque_; |
| const Origin origin_empty_; |
| |
| // Prerequisite state. |
| std::unique_ptr<content::TestBrowserContext> browser_context_; |
| std::unique_ptr<content::CacheStorageControlWrapper> |
| cache_storage_control_wrapper_; |
| scoped_refptr<content::GeneratedCodeCacheContext> |
| generated_code_cache_context_; |
| |
| // Mapping from renderer id to CodeCacheHostImpl instances being fuzzed. |
| // Access only from UI thread. |
| using UniqueCodeCacheReceiverSet = |
| std::unique_ptr<mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>, |
| base::OnTaskRunnerDeleter>; |
| std::map<int, UniqueCodeCacheReceiverSet> code_cache_host_receivers_; |
| }; |
| |
| CodeCacheHostTestcase::CodeCacheHostTestcase( |
| const content::fuzzing::code_cache_host::proto::Testcase& testcase) |
| : Testcase<ProtoTestcase, ProtoAction>(testcase), |
| origin_a_(url::Origin::Create(GURL("http://aaa.com/"))), |
| origin_b_(url::Origin::Create(GURL("http://bbb.com/"))), |
| origin_opaque_(url::Origin::Create(GURL("opaque"))), |
| origin_empty_(url::Origin::Create(GURL("file://this_becomes_empty"))) { |
| // CodeCacheHostTestcase is created on the main thread, but the actions that |
| // we want to validate the sequencing of take place on the fuzzer sequence. |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| void CodeCacheHostTestcase::SetUp(base::OnceClosure done_closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CodeCacheHostTestcase::SetUpOnUIThread, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void CodeCacheHostTestcase::SetUpOnUIThread(base::OnceClosure done_closure) { |
| browser_context_ = std::make_unique<content::TestBrowserContext>(); |
| |
| cache_storage_control_wrapper_ = |
| std::make_unique<content::CacheStorageControlWrapper>( |
| content::GetIOThreadTaskRunner({}), browser_context_->GetPath(), |
| browser_context_->GetSpecialStoragePolicy(), |
| browser_context_->GetDefaultStoragePartition() |
| ->GetQuotaManager() |
| ->proxy(), |
| content::ChromeBlobStorageContext::GetRemoteFor( |
| browser_context_.get())); |
| |
| generated_code_cache_context_ = |
| base::MakeRefCounted<content::GeneratedCodeCacheContext>(); |
| generated_code_cache_context_->Initialize(browser_context_->GetPath(), 65536); |
| |
| GetFuzzerTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CodeCacheHostTestcase::SetUpOnFuzzerThread, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void CodeCacheHostTestcase::SetUpOnFuzzerThread( |
| base::OnceClosure done_closure) { |
| mojolpm::GetContext()->StartTestcase(); |
| |
| std::move(done_closure).Run(); |
| } |
| |
| void CodeCacheHostTestcase::TearDown(base::OnceClosure done_closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CodeCacheHostTestcase::TearDownOnUIThread, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void CodeCacheHostTestcase::TearDownOnUIThread(base::OnceClosure done_closure) { |
| code_cache_host_receivers_.clear(); |
| generated_code_cache_context_.reset(); |
| cache_storage_control_wrapper_.reset(); |
| browser_context_.reset(); |
| |
| GetFuzzerTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CodeCacheHostTestcase::TearDownOnFuzzerThread, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void CodeCacheHostTestcase::TearDownOnFuzzerThread( |
| base::OnceClosure done_closure) { |
| mojolpm::GetContext()->EndTestcase(); |
| |
| std::move(done_closure).Run(); |
| } |
| |
| void CodeCacheHostTestcase::RunAction(const ProtoAction& action, |
| base::OnceClosure run_closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const auto ThreadId_UI = |
| content::fuzzing::code_cache_host::proto::RunThreadAction_ThreadId_UI; |
| const auto ThreadId_IO = |
| content::fuzzing::code_cache_host::proto::RunThreadAction_ThreadId_IO; |
| |
| switch (action.action_case()) { |
| case ProtoAction::kNewCodeCacheHost: |
| AddCodeCacheHost(action.new_code_cache_host().id(), |
| action.new_code_cache_host().render_process_id(), |
| action.new_code_cache_host().origin_id(), |
| std::move(run_closure)); |
| return; |
| |
| case ProtoAction::kRunThread: |
| // These actions ensure that any tasks currently queued on the named |
| // thread have chance to run before the fuzzer continues. |
| // |
| // We don't provide any particular guarantees here; this does not mean |
| // that the named thread is idle, nor does it prevent any other threads |
| // from running (or the consequences of any resulting callbacks, for |
| // example). |
| if (action.run_thread().id() == ThreadId_UI) { |
| content::GetUIThreadTaskRunner({})->PostTaskAndReply( |
| FROM_HERE, base::DoNothing(), std::move(run_closure)); |
| } else if (action.run_thread().id() == ThreadId_IO) { |
| content::GetIOThreadTaskRunner({})->PostTaskAndReply( |
| FROM_HERE, base::DoNothing(), std::move(run_closure)); |
| } |
| return; |
| |
| case ProtoAction::kCodeCacheHostRemoteAction: |
| mojolpm::HandleRemoteAction(action.code_cache_host_remote_action()); |
| break; |
| |
| case ProtoAction::ACTION_NOT_SET: |
| break; |
| } |
| |
| GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(run_closure)); |
| } |
| |
| void CodeCacheHostTestcase::AddCodeCacheHostImpl( |
| uint32_t id, |
| int renderer_id, |
| const net::NetworkIsolationKey& nik, |
| const blink::StorageKey& storage_key, |
| mojo::PendingReceiver<::blink::mojom::CodeCacheHost>&& receiver) { |
| auto code_cache_host = std::make_unique<content::CodeCacheHostImpl>( |
| renderer_id, generated_code_cache_context_, nik, storage_key); |
| code_cache_host->SetCacheStorageControlForTesting( |
| cache_storage_control_wrapper_.get()); |
| UniqueCodeCacheReceiverSet receivers( |
| new mojo::UniqueReceiverSet<blink::mojom::CodeCacheHost>(), |
| base::OnTaskRunnerDeleter( |
| base::SequencedTaskRunner::GetCurrentDefault())); |
| receivers->Add(std::move(code_cache_host), std::move(receiver)); |
| code_cache_host_receivers_.insert({renderer_id, std::move(receivers)}); |
| } |
| |
| static void AddCodeCacheHostInstance( |
| uint32_t id, |
| mojo::Remote<::blink::mojom::CodeCacheHost> remote, |
| base::OnceClosure run_closure) { |
| mojolpm::GetContext()->AddInstance(id, std::move(remote)); |
| |
| std::move(run_closure).Run(); |
| } |
| |
| void CodeCacheHostTestcase::AddCodeCacheHost( |
| uint32_t id, |
| int renderer_id, |
| content::fuzzing::code_cache_host::proto::NewCodeCacheHostAction::OriginId |
| origin_id, |
| base::OnceClosure run_closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| mojo::Remote<::blink::mojom::CodeCacheHost> remote; |
| auto receiver = remote.BindNewPipeAndPassReceiver(); |
| |
| const auto OriginId_B = content::fuzzing::code_cache_host::proto:: |
| NewCodeCacheHostAction_OriginId_ORIGIN_B; |
| const auto OriginId_OPAQUE = content::fuzzing::code_cache_host::proto:: |
| NewCodeCacheHostAction_OriginId_ORIGIN_OPAQUE; |
| const auto OriginId_EMPTY = content::fuzzing::code_cache_host::proto:: |
| NewCodeCacheHostAction_OriginId_ORIGIN_EMPTY; |
| |
| const Origin* origin = &origin_a_; |
| if (origin_id == OriginId_B) { |
| origin = &origin_b_; |
| } else if (origin_id == OriginId_OPAQUE) { |
| origin = &origin_opaque_; |
| } else if (origin_id == OriginId_EMPTY) { |
| origin = &origin_empty_; |
| } |
| auto storage_key = blink::StorageKey::CreateFirstParty(*origin); |
| |
| // Use of Unretained is safe since `this` is guaranteed to live at least until |
| // `run_closure` is invoked. |
| content::GeneratedCodeCacheContext::GetTaskRunner( |
| generated_code_cache_context_) |
| ->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&CodeCacheHostTestcase::AddCodeCacheHostImpl, |
| base::Unretained(this), id, renderer_id, |
| net::NetworkIsolationKey(), storage_key, |
| std::move(receiver)), |
| base::BindOnce(AddCodeCacheHostInstance, id, std::move(remote), |
| std::move(run_closure))); |
| } |
| |
| DEFINE_BINARY_PROTO_FUZZER( |
| const content::fuzzing::code_cache_host::proto::Testcase& proto_testcase) { |
| if (!proto_testcase.actions_size() || !proto_testcase.sequences_size() || |
| !proto_testcase.sequence_indexes_size()) { |
| return; |
| } |
| |
| // Make sure that the environment is initialized before we do anything else. |
| GetEnvironment(); |
| |
| CodeCacheHostTestcase testcase(proto_testcase); |
| |
| base::RunLoop main_run_loop; |
| |
| // Unretained is safe here, because `main_run_loop` has to finish before |
| // testcase goes out of scope. |
| GetFuzzerTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&mojolpm::RunTestcase<CodeCacheHostTestcase>, |
| base::Unretained(&testcase), GetFuzzerTaskRunner(), |
| main_run_loop.QuitClosure())); |
| |
| main_run_loop.Run(); |
| } |