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